Python Scrapy框架

一、安装

#Windows平台
    1、pip install wheel 
    #安装后,便支持通过wheel文件安装软件,wheel文件官网:https://www.lfd.uci.edu/~gohlke/pythonlibs
    2、下载twisted的wheel文件:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted 
    #根据Python解释器版本安装对应版本twisted文件
    3、执行pip install 下载目录\Twisted-17.9.0-cp36-cp36m-win_amd64.whl
    4、下载并安装pywin32:pip install pywin32 
    #https://sourceforge.net/projects/pywin32/files/pywin32/
    5、pip install scrapy
    

#Linux平台
    1、pip3 install scrapy

二、创建项目

scrapy startproject MyProject
cd MyProject
scrapy genspider baidu www.baidu.com

三、项目结构以及爬虫应用简介

project_name/
   scrapy.cfg
   project_name/
       __init__.py
       items.py
       pipelines.py
       settings.py
       spiders/
           __init__.py
           爬虫1.py
           爬虫2.py
           爬虫3.py

文件说明:

  • scrapy.cfg 项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。
  • items.py 设置数据存储模板,用于结构化数据,如:Django的Model
  • pipelines 数据处理行为,如:一般结构化的数据持久化
  • settings.py 配置文件,如:递归的层数、并发数,延迟下载等。强调:配置文件的选项必须大写否则视为无效****,正确写法USER_AGENT='xxxx'
  • spiders 爬虫目录,如:创建文件,编写爬虫规则

注意:一般创建爬虫文件时,以网站域名命名

四、Windows下快捷执行爬虫程序

#在项目目录下新建:start.py
from scrapy.cmdline import execute
execute(['scrapy', 'crawl', 'xiaohua'])

五、Spiders

Spiders是由一系列类(定义了一个网址或一组网址将被爬取)组成,具体包括如何执行爬取任务并且如何从页面中提取结构化的数据。

换句话说,Spiders是你为了一个特定的网址或一组网址自定义爬取和解析页面行为的地方。

  • Spiders会循环做如下事情
#1、生成初始的Requests来爬取第一个URLS,并且标识一个回调函数
第一个请求定义在start_requests()方法内默认从start_urls列表中获得url地址来生成Request请求,默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发

#2、在回调函数中,解析response并且返回值
返回值可以4种:
        包含解析数据的字典
        Item对象
        新的Request对象(新的Requests也需要指定一个回调函数)
        或者是可迭代对象(包含Items或Request)

#3、在回调函数中解析页面内容
通常使用Scrapy自带的Selectors,但很明显你也可以使用Beutifulsoup,lxml或其他你爱用啥用啥。

#4、最后,针对返回的Items对象将会被持久化到数据库
通过Item Pipeline组件存到数据库:https://docs.scrapy.org/en/latest/topics/item-pipeline.html#topics-item-pipeline)
或者导出到不同的文件(通过Feed exports:https://docs.scrapy.org/en/latest/topics/feed-exports.html#topics-feed-exports)
  • 定制scrapy.spider属性与方法详解
#1、name = 'amazon' 
定义爬虫名,scrapy会根据该值定位爬虫程序
所以它必须要有且必须唯一(In Python 2 this must be ASCII only.)

#2、allowed_domains = ['www.amazon.cn'] 
定义允许爬取的域名,如果OffsiteMiddleware启动(默认就启动),
那么不属于该列表的域名及其子域名都不允许爬取
如果爬取的网址为:https://www.example.com/1.html,那就添加'example.com'到列表.

#3、start_urls = ['http://www.amazon.cn/']
如果没有指定url,就从该列表中读取url来生成第一个请求

#4、custom_settings
值为一个字典,定义一些配置信息,在运行爬虫程序时,这些配置会覆盖项目级别的配置
所以custom_settings必须被定义成一个类属性,由于settings会在类实例化前被加载

#5、settings
通过self.settings['配置项的名字']可以访问settings.py中的配置,如果自己定义了custom_settings还是以自己的为准

#6、logger
日志名默认为spider的名字
self.logger.debug('=============>%s' %self.settings['BOT_NAME'])

#7、start_requests()
该方法用来发起第一个Requests请求,且必须返回一个可迭代的对象。它在爬虫程序打开时就被Scrapy调用,Scrapy只调用它一次。
默认从start_urls里取出每个url来生成Request(url, dont_filter=True)
        
#8、parse(response)
这是默认的回调函数,所有的回调函数必须返回an iterable of Request and/or dicts or Item objects.

#9、closed(reason)
爬虫程序结束时自动触发
  • 实现发送post请求
class MySpider(scrapy.Spider):
    name = 'myspider'
    start_urls = ['http://www.amazon.cn/']

    def start_requests(self):
        for url in start_urls:            
	        yield scrapy.FormRequest(url=url,formdata={'user': 'john', 'pass': 'secret'},callback=self.parse)

    def parse(self, response):
        pass

六、Selectors

#1 //与/
#2 text
#3、extract与extract_first:从selector对象中解出内容
#4、属性:xpath的属性加前缀@
#4、嵌套查找
#5、设置默认值
#4、按照属性查找
#5、按照属性模糊查找
#6、正则表达式
#7、xpath相对路径
#8、带变量的xpath
response.selector.css()
response.selector.xpath()
可简写为
response.css()
response.xpath()

七、 Items

https://docs.scrapy.org/en/latest/topics/items.html

八、Pipeline

  • 可以写多个Pipeline类
#1、如果优先级高的Pipeline的process_item返回一个值或者None,会自动传给下一个pipline的process_item,
#2、如果只想让第一个Pipeline执行,那得让第一个pipline的process_item抛出异常raise DropItem()

#3、可以用spider.name == '爬虫名' 来控制哪些爬虫用哪些pipeline
from scrapy.exceptions import DropItem

class CustomPipeline(object):
    def __init__(self,v):
        self.value = v

    @classmethod
    def from_crawler(cls, crawler):
        """
        Scrapy会先通过getattr判断我们是否自定义了from_crawler,有则调它来完
        成实例化
        """
        val = crawler.settings.getint('MMMM')
        return cls(val)

    def open_spider(self,spider):
        """
        爬虫刚启动时执行一次
        """
        print('000000')

    def close_spider(self,spider):
        """
        爬虫关闭时执行一次
        """
        print('111111')


    def process_item(self, item, spider):
        # 操作并进行持久化

        # return表示会被后续的pipeline继续处理
        return item

        # 表示将item丢弃,不会被后续pipeline处理
        # raise DropItem()
  • 解析图像,需要重写父类三个方法,分别是:file_path,get_media_requests,item_completed
import scrapy
from scrapy.pipelines.images import ImagesPipeline


class SpiderImagePipeline(ImagesPipeline):
    def file_path(self, request, response=None, info=None, *, item=None):
        img_name = request.url.split('/')[-1]
        print(f'{img_name}下载保存成功')
        return img_name

    def get_media_requests(self, item, info):
        yield scrapy.Request(item['img_src'])

    def item_completed(self, results, item, info):
        return item

九、Dowloader Middeware

  • 下载中间件的用途
    1、在process——request内,自定义下载,不用scrapy的下载
    2、对请求进行二次加工,比如
        设置请求头
        设置cookie
        添加代理
            scrapy自带的代理组件:
                from scrapy.downloadermiddlewares.httpproxy import HttpProxyMiddleware
                from urllib.request import getproxies
class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request: 
        :param spider: 
        :return:  
            None,继续后续中间件去下载;
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度器
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        """
        pass



    def process_response(self, request, response, spider):
        """
        spider处理完成,返回时调用
        :param response:
        :param result:
        :param spider:
        :return: 
            Response 对象:转交给其他中间件process_response
            Request 对象:停止中间件,request会被重新调度下载
            raise IgnoreRequest 异常:调用Request.errback
        """
        print('response1')
        return response

    def process_exception(self, request, exception, spider):
        """
        当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
        :param response:
        :param exception:
        :param spider:
        :return: 
            None:继续交给后续中间件处理异常;
            Response对象:停止后续process_exception方法
            Request对象:停止中间件,request将会被重新调用下载
        """
        return None
  • 配置代理
#1、与middlewares.py同级目录下新建proxy_handle.py
import requests

def get_proxy():
    return requests.get("http://127.0.0.1:5010/get/").text

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5010/delete/?proxy={}".format(proxy))
    
    

#2、middlewares.py
from Amazon.proxy_handle import get_proxy,delete_proxy

class DownMiddleware1(object):
    def process_request(self, request, spider):
        """
        请求需要被下载时,经过所有下载器中间件的process_request调用
        :param request:
        :param spider:
        :return:
            None,继续后续中间件去下载;
            Response对象,停止process_request的执行,开始执行process_response
            Request对象,停止中间件的执行,将Request重新调度器
            raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception
        """
        proxy="http://" + get_proxy()
        request.meta['download_timeout']=20
        request.meta["proxy"] = proxy
        print('为%s 添加代理%s ' % (request.url, proxy),end='')
        print('元数据为',request.meta)

    def process_response(self, request, response, spider):
        """
        spider处理完成,返回时调用
        :param response:
        :param result:
        :param spider:
        :return:
            Response 对象:转交给其他中间件process_response
            Request 对象:停止中间件,request会被重新调度下载
            raise IgnoreRequest 异常:调用Request.errback
        """
        print('返回状态吗',response.status)
        return response


    def process_exception(self, request, exception, spider):
        """
        当下载处理器(download handler)或 process_request() (下载中间件)抛出异常
        :param response:
        :param exception:
        :param spider:
        :return:
            None:继续交给后续中间件处理异常;
            Response对象:停止后续process_exception方法
            Request对象:停止中间件,request将会被重新调用下载
        """
        print('代理%s,访问%s出现异常:%s' %(request.meta['proxy'],request.url,exception))
        import time
        time.sleep(5)
        delete_proxy(request.meta['proxy'].split("//")[-1])
        request.meta['proxy']='http://'+get_proxy()

        return request
  • 结合selenium获取动态数据

    分析:当引擎将url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的数据的。要想获取动态加载的数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的数据,然后将被篡改的response对象最终交给Spiders进行解析操作。

# 爬虫文件
class WangyiSpider(RedisSpider):
    name = 'wangyi'
    #allowed_domains = ['www.xxxx.com']
    start_urls = ['https://news.163.com']
    def __init__(self):
        #实例化一个浏览器对象(实例化一次)
        self.bro = webdriver.Chrome(executable_path='/Users/bobo/Desktop/chromedriver')

    #必须在整个爬虫结束后,关闭浏览器
    def closed(self,spider):
        print('爬虫结束')
        self.bro.quit()
        
        
# 中间件文件
from scrapy.http import HtmlResponse    
    #参数介绍:
    #拦截到响应对象(下载器传递给Spider的响应对象)
    #request:响应对象对应的请求对象
    #response:拦截到的响应对象
    #spider:爬虫文件中对应的爬虫类的实例
    def process_response(self, request, response, spider):
        #响应对象中存储页面数据的篡改
        if request.url in['http://news.163.com/domestic/','http://news.163.com/world/','http://news.163.com/air/','http://war.163.com/']:
            spider.bro.get(url=request.url)
            js = 'window.scrollTo(0,document.body.scrollHeight)'
            spider.bro.execute_script(js)
            time.sleep(2)  #一定要给与浏览器一定的缓冲加载数据的时间
            #页面数据就是包含了动态加载出来的新闻数据对应的页面数据
            page_text = spider.bro.page_source
            #篡改响应对象
            return HtmlResponse(url=spider.bro.current_url,body=page_text,encoding='utf-8',request=request)
        else:
            return response
            
            
            
# 配置文件
DOWNLOADER_MIDDLEWARES = {
    'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543,

}

十、Setting

# 常用配置
ROBOTSTXT_OBEY = False
LOG_LEVEL = 'ERROR'
USER_AGENT = 'Amazon (+http://www.yourdomain.com)'
IMAGES_STORE = 'images'


#==>第一部分:基本配置<===
#1、项目名称,默认的USER_AGENT由它来构成,也作为日志记录的日志名
BOT_NAME = 'Amazon'

#2、爬虫应用路径
SPIDER_MODULES = ['Amazon.spiders']
NEWSPIDER_MODULE = 'Amazon.spiders'

#3、客户端User-Agent请求头
USER_AGENT = 'Amazon (+http://www.yourdomain.com)'

#4、是否遵循爬虫协议
# Obey robots.txt rules
ROBOTSTXT_OBEY = False

#5、是否支持cookie,cookiejar进行操作cookie,默认开启
COOKIES_ENABLED = False

#6、Telnet用于查看当前爬虫的信息,操作爬虫等...使用telnet ip port ,然后通过命令操作
TELNETCONSOLE_ENABLED = False
TELNETCONSOLE_HOST = '127.0.0.1'
TELNETCONSOLE_PORT = [6023,]

#7、Scrapy发送HTTP请求默认使用的请求头
DEFAULT_REQUEST_HEADERS = {
   'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
   'Accept-Language': 'en',
}



#===>第二部分:并发与延迟<===
#1、下载器总共最大处理的并发请求数,默认值16
CONCURRENT_REQUESTS = 32

#2、每个域名能够被执行的最大并发请求数目,默认值8
CONCURRENT_REQUESTS_PER_DOMAIN = 16

#3、能够被单个IP处理的并发请求数,默认值0,代表无限制,需要注意两点
#I、如果不为零,那CONCURRENT_REQUESTS_PER_DOMAIN将被忽略,即并发数的限制是按照每个IP来计算,而不是每个域名
#II、该设置也影响DOWNLOAD_DELAY,如果该值不为零,那么DOWNLOAD_DELAY下载延迟是限制每个IP而不是每个域
CONCURRENT_REQUESTS_PER_IP = 16

#4、如果没有开启智能限速,这个值就代表一个规定死的值,代表对同一网址延迟请求的秒数
DOWNLOAD_DELAY = 3


#===>第三部分:智能限速/自动节流:AutoThrottle extension<===
#一:介绍
from scrapy.contrib.throttle import AutoThrottle #http://scrapy.readthedocs.io/en/latest/topics/autothrottle.html#topics-autothrottle
设置目标:
1、比使用默认的下载延迟对站点更好
2、自动调整scrapy到最佳的爬取速度,所以用户无需自己调整下载延迟到最佳状态。用户只需要定义允许最大并发的请求,剩下的事情由该扩展组件自动完成


#二:如何实现?
在Scrapy中,下载延迟是通过计算建立TCP连接到接收到HTTP包头(header)之间的时间来测量的。
注意,由于Scrapy可能在忙着处理spider的回调函数或者无法下载,因此在合作的多任务环境下准确测量这些延迟是十分苦难的。 不过,这些延迟仍然是对Scrapy(甚至是服务器)繁忙程度的合理测量,而这扩展就是以此为前提进行编写的。


#三:限速算法
自动限速算法基于以下规则调整下载延迟
#1、spiders开始时的下载延迟是基于AUTOTHROTTLE_START_DELAY的值
#2、当收到一个response,对目标站点的下载延迟=收到响应的延迟时间/AUTOTHROTTLE_TARGET_CONCURRENCY
#3、下一次请求的下载延迟就被设置成:对目标站点下载延迟时间和过去的下载延迟时间的平均值
#4、没有达到200个response则不允许降低延迟
#5、下载延迟不能变的比DOWNLOAD_DELAY更低或者比AUTOTHROTTLE_MAX_DELAY更高

#四:配置使用
#开启True,默认False
AUTOTHROTTLE_ENABLED = True
#起始的延迟
AUTOTHROTTLE_START_DELAY = 5
#最小延迟
DOWNLOAD_DELAY = 3
#最大延迟
AUTOTHROTTLE_MAX_DELAY = 10
#每秒并发请求数的平均值,不能高于 CONCURRENT_REQUESTS_PER_DOMAIN或CONCURRENT_REQUESTS_PER_IP,调高了则吞吐量增大强奸目标站点,调低了则对目标站点更加”礼貌“
#每个特定的时间点,scrapy并发请求的数目都可能高于或低于该值,这是爬虫视图达到的建议值而不是硬限制
AUTOTHROTTLE_TARGET_CONCURRENCY = 16.0
#调试
AUTOTHROTTLE_DEBUG = True
CONCURRENT_REQUESTS_PER_DOMAIN = 16
CONCURRENT_REQUESTS_PER_IP = 16



#===>第四部分:爬取深度与爬取方式<===
#1、爬虫允许的最大深度,可以通过meta查看当前深度;0表示无深度
 DEPTH_LIMIT = 3

#2、爬取时,0表示深度优先Lifo(默认);1表示广度优先FiFo

# 后进先出,深度优先
 DEPTH_PRIORITY = 0
 SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue'
 SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'
# 先进先出,广度优先

 DEPTH_PRIORITY = 1
 SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue'
 SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue'


#3、调度器队列
 SCHEDULER = 'scrapy.core.scheduler.Scheduler'
 from scrapy.core.scheduler import Scheduler

#4、访问URL去重
 DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl'



#===>第五部分:中间件、Pipelines、扩展<===
#1、Enable or disable spider middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html
SPIDER_MIDDLEWARES = {
    'Amazon.middlewares.AmazonSpiderMiddleware': 543,
}

#2、Enable or disable downloader middlewares
# See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
DOWNLOADER_MIDDLEWARES = {
    'Amazon.middlewares.DownMiddleware1': 543,
}

#3、Enable or disable extensions
# See http://scrapy.readthedocs.org/en/latest/topics/extensions.html
EXTENSIONS = {
    'scrapy.extensions.telnet.TelnetConsole': None,
}

#4、Configure item pipelines
# See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
    'Amazon.pipelines.CustomPipeline': 200,
}



#===>第六部分:缓存<===
"""
1. 启用缓存
    目的用于将已经发送的请求或相应缓存下来,以便以后使用
    
    from scrapy.downloadermiddlewares.httpcache import HttpCacheMiddleware
    from scrapy.extensions.httpcache import DummyPolicy
    from scrapy.extensions.httpcache import FilesystemCacheStorage
"""
# 是否启用缓存策略
 HTTPCACHE_ENABLED = True

# 缓存策略:所有请求均缓存,下次在请求直接访问原来的缓存即可
 HTTPCACHE_POLICY = "scrapy.extensions.httpcache.DummyPolicy"
# 缓存策略:根据Http响应头:Cache-Control、Last-Modified 等进行缓存的策略
 HTTPCACHE_POLICY = "scrapy.extensions.httpcache.RFC2616Policy"

# 缓存超时时间
 HTTPCACHE_EXPIRATION_SECS = 0

# 缓存保存路径
 HTTPCACHE_DIR = 'httpcache'

# 缓存忽略的Http状态码
 HTTPCACHE_IGNORE_HTTP_CODES = []

# 缓存存储的插件
 HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'


#===>第七部分:线程池<===
REACTOR_THREADPOOL_MAXSIZE = 10

#Default: 10
#scrapy基于twisted异步IO框架,downloader是多线程的,线程数是Twisted线程池的默认大小(The maximum limit for Twisted Reactor thread pool size.)

#关于twisted线程池:
http://twistedmatrix.com/documents/10.1.0/core/howto/threading.html

#线程池实现:twisted.python.threadpool.ThreadPool
twisted调整线程池大小:
from twisted.internet import reactor
reactor.suggestThreadPoolSize(30)

#scrapy相关源码:
D:\python3.6\Lib\site-packages\scrapy\crawler.py

#补充:
windows下查看进程内线程数的工具:
    https://docs.microsoft.com/zh-cn/sysinternals/downloads/pslist
    或
    https://pan.baidu.com/s/1jJ0pMaM
    
    命令为:
    pslist |findstr python

linux下:top -p 进程id

十一、深度爬取

import scrapy

from ..items import DeepspiderItem


class DeepSpiderSpider(scrapy.Spider):
    name = "deep_spider"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://wz.sun0769.com/political/index/politicsNewest?id=1&page=1"]
    base_url = 'https://wz.sun0769.com/political/index/politicsNewest?id=1&page={0}'
    page = 2

    def parse(self, response):
        li_list = response.xpath('/html/body/div[2]/div[3]/ul[2]/li')
        for li in li_list:
            item = DeepspiderItem()
            title = li.xpath('./span[3]/a/text()').extract_first()
            item['title'] = title
            detail_url = 'https://wz.sun0769.com/' + li.xpath('./span[3]/a/@href').extract_first()
            yield scrapy.Request(url=detail_url, callback=self.parse_detail, meta={'item': item})

        if self.page <= 5:
            new_url = self.base_url.format(self.page)
            self.page += 1
            yield scrapy.Request(url=new_url, callback=self.parse)

    def parse_detail(self, response):
        content = response.xpath('/html/body/div[3]/div[2]/div[2]/div[2]/pre/text()').extract_first()
        item = response.meta['item']
        item['content'] = content
        yield item

十二、CrawlSpider全站爬取

# 1、创建项目
# scrapy startproject name
# cd ./name
# scrapy genspider -t crawl name www.xxx.com



import scrapy

from ..items import AllpagesspiderItem

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule


class AllPagesSpiderSpider(CrawlSpider):
    name = "all_pages_spider"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://sc.chinaz.com/jianli/free.html"]

    rules = (Rule(LinkExtractor(allow=r"free_\d+\.html"), callback="parse_item", follow=True),)

    # 起始页
    def parse_start_url(self, response, **kwargs):
        # print(dir(response))
        div_list = response.xpath('//*[@id="container"]/div')
        for div in div_list:
            item = AllpagesspiderItem()
            title = div.xpath('./p/a/text()').extract_first()
            item['title'] = title
            detail_url = div.xpath('./a/@href').extract_first()
            yield scrapy.Request(url=response.urljoin(detail_url), callback=self.detail_parse, meta={'item': item})

    # 后续全站页面
    def parse_item(self, response):
        div_list = response.xpath('//*[@id="container"]/div')
        for div in div_list:
            item = AllpagesspiderItem()
            title = div.xpath('./p/a/text()').extract_first()
            item['title'] = title
            detail_url = div.xpath('./a/@href').extract_first()
            yield scrapy.Request(url=response.urljoin(detail_url), callback=self.detail_parse, meta={'item': item})

    def detail_parse(self, response):
        item = response.meta['item']
        item['download_link'] = response.xpath('//*[@id="down"]/div[2]/ul/li[1]/a/@href').extract_first()
        yield item

十三、增量式爬虫

  • 基于关键字md5存储判断更新(表面爬取)
# spider
import hashlib

import redis
import scrapy

from ..items import ZlsspiderItem


class ZlsSpiderSpider(scrapy.Spider):
    name = "zls_spider"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://sc.chinaz.com/jianli/free.html"]
    conn = redis.Redis(host='127.0.0.1', port=6379)

    def parse(self, response):
        div_list = response.xpath('//*[@id="container"]/div')
        for div in div_list:
            title = div.xpath('./p/a/text()').extract_first()
            detail_url = div.xpath('./p/a/@href').extract_first()
            hash_str = title + detail_url
            md5_obj = hashlib.md5()
            md5_obj.update(hash_str.encode('utf-8'))
            md5_str = md5_obj.hexdigest()
            if self.conn.sadd('data_id', md5_str):
                print('正在更新数据...')
                item = ZlsspiderItem()
                item['title'] = title
                item['detail_url'] = detail_url
                yield item
            else:
                print('暂无更新!')

                
# pipeline                
import json
class ZlsspiderPipeline:
    def process_item(self, item, spider):
        conn = spider.conn
        item_dict = {key: item[key] for key in item}
        conn.lpush('item_data', json.dumps(item_dict))
        return item
  • 基于url存储判断更新(深度爬取)
# spider
import redis
import scrapy

from ..items import ZlsspiderItem


class ZlsSpiderSpider(scrapy.Spider):
    name = "zls_spider"
    # allowed_domains = ["www.xxx.com"]
    start_urls = ["https://sc.chinaz.com/jianli/free.html"]
    conn = redis.Redis(host='127.0.0.1', port=6379)
    page = 1
    is_update = False
    base_url = 'https://sc.chinaz.com/jianli/free_{0}.html'

    def parse(self, response):
        div_list = response.xpath('//*[@id="container"]/div')
        for div in div_list:
            title = div.xpath('./p/a/text()').extract_first()
            detail_url = div.xpath('./p/a/@href').extract_first()
            if self.conn.sadd('url_id', detail_url):
                self.is_update = True
                item = ZlsspiderItem()
                item['title'] = title
                yield scrapy.Request(url=response.urljoin(detail_url), callback=self.detail_parse, meta={'item': item})

		# 爬取多页
        if self.page < 5:
            self.page += 1
            yield scrapy.Request(url=response.urljoin(self.base_url.format(self.page)), callback=self.parse)

    # 解析详情页下载url
    def detail_parse(self, response):
        item = response.meta['item']
        download_url = response.xpath('//*[@id="down"]/div[2]/ul/li[1]/a/@href').extract_first()
        item['download_url'] = response.urljoin(download_url)
        yield item

        
        
        
        
        
        
# pipeline
import json

import scrapy
from scrapy.pipelines.files import FilesPipeline


class ZlsspiderPipeline(FilesPipeline):
    def get_media_requests(self, item, info):
        yield scrapy.Request(url=item['download_url'], meta={'item': item})

    def file_path(self, request, response=None, info=None, *, item=None):
        file_name = request.meta['item']['title']
        file_type = request.url.split('.')[-1]
        print(f'{file_name}.{file_type}下载成功')
        return f'{file_name}.{file_type}'

    def item_completed(self, results, item, info):
        return item


class Zls2spiderPipeline:
    def process_item(self, item, spider):
        conn = spider.conn
        item_dic = {key: item[key] for key in item}
        conn.lpush('item_data', json.dumps(item_dic))
        return item

    def close_spider(self, spider):
        if spider.is_update:
            print('数据更新成功!')
        else:
            print('暂无更新!')
posted @ 2023-03-22 00:50  与鹿逐秋  阅读(30)  评论(0编辑  收藏  举报