Scrapy框架2
一、进程、线程、协成
1、进程、线程、协成之间的关系
1、 线程是计算机中最小的工作单元。
2、 进程是提供资源供n个线程使用,即进程是最小的管理单元。
3、协程是人为控制的线程。
4、总结:1、python中由于有 GIL锁的存在,所以一个进程中同一时刻只有一个线程被CPU调度,所以在计算密集型,使用多进程而在io密集型使用多线程。
2、使用协成可以实现单线程下的并发,线程中cpu在遇到io操作时就会切到下一个线程中,这样大大的影响的效率于是人为的控制cpu在线程中的切换,当cpu在线程中遇到io会切换到该线程的下一个程序中这就是协成
2、异步非阻塞模块
1、gevent模块,基于协程的异步非阻塞模块。
2、Twisted,基于事件驱动(while循环检测)异步非阻塞模块(框架)。
3、非阻塞是指不等待 ,异步是指回调,执行某个任务完成之后,自动执行的函数。回调函数
二、scarpy框架
1、重写scrapy请求头方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | # -*- coding: utf-8 -*- import scrapy from scrapy.http import Request from scrapy.selector import HtmlXPathSelector #response= HtmlXPathSelector(response)主要是在parse函数中使用,from urllib.parse import urlencode from ..items import MyscrapyItem class BaiduSpider(scrapy.Spider): name = 'baidu' #爬虫应用的名称,通过此名称启动爬虫命令 allowed_domains = [ 'www.amazon.cn' ] # 允许爬取数据的域名 def __init__( self ,keywords = None , * args, * * kwargs): super (BaiduSpider, self ).__init__( * args, * * kwargs) self .keywords = keywords #主要是接受一些外部传进来的参数 def start_requests( self ): #重写scrapy请求头url必须写这个名字的函数 url = 'https://www.amazon.cn/s/ref=nb_sb_noss_1?' #新url parmas = { 'field-keywords' : self .keywords #主要是定义一些外部传进来的参数 } url = url + urlencode(parmas,encoding = 'utf-8' ) #路径拼接,形成一个完整的url路径 yield Request(url,callback = self .parse_index) #yield返回数据给回调函数,也可以是return+[]的方式返回数据给回调函数,yield返回的是生成器,return+[]返回的可迭代对象 # return [Request(url='www.baidu.com',callback=self.parse),] def parse_index( self , response): urls = response.xpath( '//*[contains(@id,"result_"]/div/div[3]/div[1]/a/@href' ).extract() for url in urls: print (url) yield Request(url,callback = self .parse) #返回详情页的信息 next_url = response.urljoin(response.xpath( '//*[@id="pagnNextLink"]/@href' ).extract_first()) yield Request(next_url,callback = self .parse_index) #返回下一页的url路径,返回的是个Request对象 def parse( self , response): price = response.xpath( '//*[@id="priceblock_ourprice"]/text()' ).extract_first().strip() colour = response.xpath( '//*[@id="variation_color_name"]/div/span/text()' ).extract_first().strip() item = MyscrapyItem() item[ 'price' ] = price item[ 'colour' ] = colour return item #返回的是个item对象 或者是这种格式,只要返回的是个item对象及格 obj = XiaoHuarItem(price=price, colour=colour, ) yield obj |
2、items文件写法
1 2 3 4 | import scrapy class MyscrapyItem(scrapy.Item): price = scrapy.Field() colour = scrapy.Field() |
3、自定义pipelines文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from scrapy.exceptions import DropItem #主动触发异常用的 class CustomPipeline( object ): def __init__( self ,val): self .vale = val #构造一个对象特有的方法 def process_item( self , item, spider): #用于存储爬取的数据,可以存储在文件也可以存储在数据库。 f = opne( 'item.log' , 'a+' )<br> f.write(item.price,item.colour)<br> f.close() return item # return表示会被后续的pipeline继续处理,因为有可以一个爬虫项目会有多个爬虫程序一起执行,但是都有写入同一个文件中,那就让第一个文件写入,其余后面的文件不用重复写入。 # raise DropItem() # 表示将item丢弃,不会被后续pipeline处理 # if spider.name='baidu': #这就表示该文件只是存储这个名字的爬虫爬取的数据<br> #f=opne('item.log','a+')<br> #f.write(item.price,item.colour)<br> #f.close()<br> #return item<br> @classmethod def from_crawler( cls , crawler): #初始化时候,用于创建pipeline对象,crawler这个类可以得到settings配置文件内的数据,如果需要给pipeline对象创建某个额外的参数就可以用这中方法 val = crawler.settings.getint( 'MMMM' ) #val = crawler.settings.get('MMMM') 读取配置文件的某个信息 return cls (val) def open_spider( self ,spider): #在爬虫程序开始执行之前调用该方法 print ( '000000' ) def close_spider( self ,spider): #在爬虫程序关闭后调用该方法 print ( '111111' )<br><br>总结:这五个方法的执行顺序: 先执行from_crawler方法,用于初始化数据的时候读取配置文件,然后再执行爬虫程序的__init__方法,然后程序启动前执行open_spider方法,程序结束前保存数据时执行process_item方法,程序结果后<br>执行close_spider方法。 |
4、自定义url去重功能
1 2 3 | scrapy默认使用 scrapy.dupefilter.RFPDupeFilter 进行去重,settings相关配置有: DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' #去重文件,可以自定义 DUPEFILTER_DEBUG = False #默认去重功能是关闭的可以在settings中打开去重功能也可以在爬虫程序中手动指定去重 yield Request(url,callback=self.parse,dont_filter=True) JOBDIR = "保存范文记录的日志路径,如:/root/" # 最终路径为 /root/requests.seen 默认url是存放在内存中的,只要添加上jobdir就可以吧url存放在指定文件中 |
自己写一个去重规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class RepeatUrl: def __init__( self ): self .visited_url = set () #默认是一个集合,也可以是自定义链接一个数据库,文件等 @classmethod def from_settings( cls , settings): """ 初始化时,调用 """ return cls () def request_seen( self , request): """ 检测当前请求是否已经被访问过 :param request: :return: True表示已经访问过;False表示未访问过 """ if request.url in self .visited_url: return True self .visited_url.add(request.url) return False def open ( self ): """ 开始爬去请求时,调用 :return: """ print ( 'open replication' ) def close( self , reason): """ 结束爬虫爬取时,调用 :param reason: :return: """ print ( 'close replication' ) def log( self , request, spider): """ 记录日志 :param request: :param spider: :return: """ print ( 'repeat' , request.url)<br>注意:写好自定义的去重规则后要将该文件设置到settings中该规程才能生效 比如:DUPEFILTER_CLASS = 'scrapy.dupefilter.RFPDupeFilter' |
5、自定义命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #自定制一键启动所有爬虫命令 from scrapy.commands import ScrapyCommand from scrapy.utils.project import get_project_settings class Command(ScrapyCommand): requires_project = True def syntax( self ): return '[options]' def short_desc( self ): return 'Runs all of the spiders' def run( self , args, opts): #这是源码的入口 spider_list = self .crawler_process.spiders. list () for name in spider_list: #循环所有的爬虫程序名称 self .crawler_process.crawl(name, * * opts.__dict__) #准备所有的爬虫程序 self .crawler_process.start() #开始爬取任务注意:1、在spiders同级创建任意目录,如:commands 2、在其中创建 crawlall.py 文件 (此处文件名就是自定义的命令) 3、在settings.py 中添加配置 COMMANDS_MODULE = '项目名称.目录名称' 4、在项目目录执行命令:scrapy crawlall #自定制在py文件中启动某个爬虫命令 from scrapy.cmdline import execute execute(['scrapy', '命令', '爬虫程序名称','-a','传入的参数']) eg:execute(['scrapy', 'crawl', 'baidu','-a','keywords=iphone7'])注意:自定制的py文件名称必须是entrypoint.py |
6、自定义信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from scrapy import signals class MyExtension( object ): def __init__( self , value): self .value = value #定义自己想要的方法 @classmethod def from_crawler( cls , crawler): #扩展功能,在扩展功能里注册信号 val = crawler.settings.getint( 'MMMM' ) #读取配置文件信息 ext = cls (val) crawler.signals.connect(ext.spider_opened, signal = signals.spider_opened) #注册信号 crawler.signals.connect(ext.spider_closed, signal = signals.spider_closed) #注册信号,可以注册很多信号,看源码 return ext #如果不需要扩展功能那么就直接注册信号即可 def spider_opened( self , spider): #爬虫程序启动时触发的信号 print ( 'open' ) def spider_closed( self , spider): #爬虫程序关闭时触发的信号 print ( 'close' )注释: 1 、在项目目录下创建一个py文件,将自定义的信号写入该文件,建议py文件名为extensions 2 、在settings中注册自定义信号的文件 eg:EXTENSIONS = { 'scrapy.extensions.telnet.TelnetConsole' : 300 ,} |
7、中间件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #下载中间件class DownMiddleware1(object): def process_request( self , request, spider): #请求过去的中间件带的时request """ 请求需要被下载时,经过所有下载器中间件的process_request调用 return: None,继续后续中间件去下载,默认就是什么都不返回; Response对象,停止process_request的执行,开始执行process_response,先导入Response(from scrapy.http import Response) eg:return Response(url='http://www.baidu.com',request=request) #只要返回Response对象就不会执行后续的Request,而是直接执行Response Request对象,停止中间件的执行,将Request重新调度器 #如果返回的是Request对象那就不会执行后续的Request和Reques而是直接到引擎,重新调度 raise IgnoreRequest异常,停止process_request的执行,开始执行process_exception """ pass def process_response( self , request, response, spider): #返回数据的中间件带的时response """ spider处理完成,返回时调用,默认返回的就是Response return: Response 对象:转交给其他中间件process_response Request 对象:停止中间件,request会被重新调度下载 raise IgnoreRequest 异常:调用Request.errback """ return response def process_exception( self , request, exception, spider): """ 当下载处理器(download handler)或 process_request() (下载中间件)抛出异常的时候调用 :return: None:继续交给后续中间件处理异常; Response对象:停止后续process_exception方法,正确执行后续方法 Request对象:停止中间件后续方法,request将会被重新调用下载 """ return None 注释: 1 、在项目目录下创建.py文件,将中间件代码写在该文件中,建议文件名称为DownMiddleware 2 、在settings中注册中间件,DOWNLOADER_MIDDLEWARES = { 'chouti.middlewares.MyCustomDownloaderMiddleware' : 600 ,},写了几个中间件就注册几个中间件 3 、可以在中间件的process_request中将所有的Request请求添加上请求头,添加cookies等操作,也可以添加代理,判断那个Request需要代理。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #爬虫中间件 class SpiderMiddleware( object ): def process_spider_input( self ,response, spider): """下载完成,执行,然后交给parse处理,默认返回none""" pass def process_spider_output( self ,response, result, spider): """spider处理完成,返回时调用,return: 必须返回包含 Request 或 Item 对象的可迭代对象(iterable)""" return result def process_spider_exception( self ,response, exception, spider): """异常调用,return: None,继续交给后续中间件处理异常;含 Response 或 Item 的可迭代对象(iterable),交给调度器或pipeline""" return None def process_start_requests( self ,start_requests, spider): """爬虫启动时调用,return: 包含 Request 对象的可迭代对象,由于爬虫只是启动一次所以process_start_requests也只是调用一次 """ return start_requests注释: 1 、在项目目录下创建.py文件,将中间件代码写在该文件中,建议文件名称为SpiderMiddleware 2 、在settings中注册中间件,SPIDER_MIDDLEWARES = { 'chouti.middlewares.ChoutiSpiderMiddleware' : 600 ,},写了几个中间件就注册几个中间件 |
8、自定义代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 代理,需要在环境变量中设置 from scrapy.contrib.downloadermiddleware.httpproxy import HttpProxyMiddleware 方式一:使用默认<br> import os os.environ {<br> http_proxy:http: / / root:woshiniba@ 192.168 . 11.11 : 9999 / #有的代理链接是需要用户名和密码的,这个就是root为用户名,woshiniba为密码 https_proxy:http: / / 192.168 . 11.11 : 9999 / <br> } #该方法只需要在爬虫程序文件的类的__init__中添加上就可以了,程序会判断只要是_prxoy结尾的就会吧这个代理拿出来<br> 方式二:使用自定义下载中间件 def to_bytes(text, encoding = None , errors = 'strict' ): if isinstance (text, bytes): return text if not isinstance (text, six.string_types): raise TypeError( 'to_bytes must receive a unicode, str or bytes ' 'object, got %s' % type (text).__name__) if encoding is None : encoding = 'utf-8' return text.encode(encoding, errors) class ProxyMiddleware( object ): #代理中间件 def process_request( self , request, spider): PROXIES = [ { 'ip_port' : '111.11.228.75:80' , 'user_pass' : ''}, { 'ip_port' : '120.198.243.22:80' , 'user_pass' : ''}, { 'ip_port' : '111.8.60.9:8123' , 'user_pass' : ''}, { 'ip_port' : '101.71.27.120:80' , 'user_pass' : ''}, { 'ip_port' : '122.96.59.104:80' , 'user_pass' : ''}, { 'ip_port' : '122.224.249.122:8088' , 'user_pass' : ''}, ] proxy = random.choice(PROXIES) #随机选择一个代理,高明就高明在这个地方,默认代理是不能随机生成的,所有也会有被封的风险。 if proxy[ 'user_pass' ] is not None : request.meta[ 'proxy' ] = to_bytes( "http://%s" % proxy[ 'ip_port' ]) #添加上代理 encoded_user_pass = base64.encodestring(to_bytes(proxy[ 'user_pass' ])) #代理加密,固定加密方法 request.headers[ 'Proxy-Authorization' ] = to_bytes( 'Basic ' + encoded_user_pass) #代理授权 #print "**************ProxyMiddleware have pass************" + proxy['ip_port'] else : #print "**************ProxyMiddleware no pass************" + proxy['ip_port'] request.meta[ 'proxy' ] = to_bytes( "http://%s" % proxy[ 'ip_port' ]) #添加代理 注释: 1 、在爬虫项目路径中创建代理文件<br> 2 、在settings中注册上代理 DOWNLOADER_MIDDLEWARES = { 'step8_king.middlewares.ProxyMiddleware' : 500 ,} |
9、settings配置文件详解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 1. 爬虫名称 BOT_NAME = 'step8_king' 2. 爬虫应用路径 SPIDER_MODULES = [ 'step8_king.spiders' ] #其实就是创建的爬虫程序名称+spiders NEWSPIDER_MODULE = 'step8_king.spiders' <br> 3. 客户端 user - agent请求头 # USER_AGENT = 'step8_king (+http://www.yourdomain.com)' 4. 禁止爬虫配置(机器人协议) # ROBOTSTXT_OBEY = True<br> 5. 并发请求数 # CONCURRENT_REQUESTS = 4 #一个线程最大能并发多少个请求,力度粗 6. 延迟下载秒数 # DOWNLOAD_DELAY = 2 默认是秒,每多少秒一次下载 7. 单域名访问并发数,并且延迟下次秒数也应用在每个域名 力度细 # CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 单IP访问并发数,如果有值则忽略:CONCURRENT_REQUESTS_PER_DOMAIN,并且延迟下次秒数也应用在每个IP 力度细 # CONCURRENT_REQUESTS_PER_IP = 3 8. 是否支持cookie,cookiejar进行操作cookie # COOKIES_ENABLED = True # COOKIES_DEBUG = True<br> 9. Telnet用于查看当前爬虫的信息,操作爬虫等... # 使用telnet ip port ,然后通过命令操作 # TELNETCONSOLE_ENABLED = True # TELNETCONSOLE_HOST = '127.0.0.1' # TELNETCONSOLE_PORT = [6023,] 10. 默认请求头,死板 # Override the default request headers: # DEFAULT_REQUEST_HEADERS = { # 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # 'Accept-Language': 'en', # } 11. 定义pipeline处理请求 # ITEM_PIPELINES = { # 'step8_king.pipelines.JsonPipeline': 700, # 'step8_king.pipelines.FilePipeline': 500, # } 12. 自定义扩展,基于信号进行调用 # Enable or disable extensions # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html # EXTENSIONS = { # # 'step8_king.extensions.MyExtension': 500, # } 13. 爬虫允许的最大深度,可以通过meta查看当前深度; 0 表示无深度 # DEPTH_LIMIT = 3 14. 爬取时, 0 表示深度优先Lifo(默认); 1 表示广度优先FiFo # 后进先出,深度优先 # DEPTH_PRIORITY = 0 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleLifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.LifoMemoryQueue'<br> # 先进先出,广度优先 # DEPTH_PRIORITY = 1 # SCHEDULER_DISK_QUEUE = 'scrapy.squeue.PickleFifoDiskQueue' # SCHEDULER_MEMORY_QUEUE = 'scrapy.squeue.FifoMemoryQueue' 15. 调度器队列 # SCHEDULER = 'scrapy.core.scheduler.Scheduler' # from scrapy.core.scheduler import Scheduler 16. 访问URL去重 # DUPEFILTER_CLASS = 'step8_king.duplication.RepeatUrl' """ 17. 自动限速算法 from scrapy.contrib.throttle import AutoThrottle 自动限速设置 1. 获取最小延迟 DOWNLOAD_DELAY 2. 获取最大延迟 AUTOTHROTTLE_MAX_DELAY 3. 设置初始下载延迟 AUTOTHROTTLE_START_DELAY 4. 当请求下载完成后,获取其"连接"时间 latency,即:请求连接到接受到响应头之间的时间 5. 用于计算的... AUTOTHROTTLE_TARGET_CONCURRENCY target_delay = latency / self.target_concurrency new_delay = (slot.delay + target_delay) / 2.0 # 表示上一次的延迟时间 new_delay = max(target_delay, new_delay) new_delay = min(max(self.mindelay, new_delay), self.maxdelay) slot.delay = new_delay """ |
10、https证书设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | Https访问时有两种情况: 1. 要爬取网站使用的可信任证书(默认支持) DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "scrapy.core.downloader.contextfactory.ScrapyClientContextFactory" 2. 要爬取网站使用的自定义证书 DOWNLOADER_HTTPCLIENTFACTORY = "scrapy.core.downloader.webclient.ScrapyHTTPClientFactory" DOWNLOADER_CLIENTCONTEXTFACTORY = "step8_king.https.MySSLFactory" # https.py from scrapy.core.downloader.contextfactory import ScrapyClientContextFactory from twisted.internet.ssl import (optionsForClientTLS, CertificateOptions, PrivateCertificate) class MySSLFactory(ScrapyClientContextFactory): def getCertificateOptions( self ): from OpenSSL import crypto v1 = crypto.load_privatekey(crypto.FILETYPE_PEM, open ( '/Users/wupeiqi/client.key.unsecure' , mode = 'r' ).read()) #打开本地保存的证书文件 v2 = crypto.load_certificate(crypto.FILETYPE_PEM, open ( '/Users/wupeiqi/client.pem' , mode = 'r' ).read()) return CertificateOptions( privateKey = v1, # pKey对象 certificate = v2, # X509对象 verify = False , method = getattr ( self , 'method' , getattr ( self , '_ssl_method' , None )) ) 其他: 相关类 scrapy.core.downloader.handlers.http.HttpDownloadHandler scrapy.core.downloader.webclient.ScrapyHTTPClientFactory scrapy.core.downloader.contextfactory.ScrapyClientContextFactory 相关配置 DOWNLOADER_HTTPCLIENTFACTORY DOWNLOADER_CLIENTCONTEXTFACTORY |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步