请求传参
请求传参
在某些情况下,我们爬取的数据不在同一个页面中,例如,我们爬取一个电影网站,电影的名称,评分在一级页面,而要爬取的其他电影详情在其二级子页面中。这时我们就需要用到请求传参。
请求传参的使用场景:当我们使用爬虫爬取的数据没有存在于同一张页面的时候,则必须使用请求传参。
案例:爬取前程无忧的职业名称与职业信息
bossPro.py
import scrapy from boss.items import BossItem class BossproSpider(scrapy.Spider): name = 'bossPro' # allowed_domains = ['www.zhipin.com'] start_urls = ['https://search.51job.com/list/080200,000000,0000,00,9,99,Python,2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0%2C0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='] url = 'https://search.51job.com/list/080200,000000,0000,00,9,99,Python,2,%d.html' page_num = 1 def parse(self, response): li_list = response.xpath('//*[@id="resultList"]/div[@class="el"]') for li in li_list: item = BossItem() li_name =li.xpath('./p/span[1]/a/text()').extract_first() detail_url =li.xpath('./p/span[1]/a/@href').extract_first() item['li_name'] = li_name # 请求传参:mate={},可以将meta字典传递给请求对应的回调函数 yield scrapy.Request(detail_url,callback=self.parse_detail,meta={'item':item}) # 分页处理 if self.page_num <=3: self.page_num += 1 new_user = format(self.url%self.page_num) yield scrapy.Request(new_user,callback=self.parse) def parse_detail(self,response): item = response.meta['item'] detail_info = response.xpath('/html/body/div[3]/div[2]/div[3]/div[1]//text()').extract() detail_info = ' '.join(detail_info) item['detail_info'] = detail_info yield item
items.py
import scrapy class BossItem(scrapy.Item): # define the fields for your item here like: # name = scrapy.Field() li_name = scrapy.Field() detail_info = scrapy.Field()
pipelines.py
class BossPipeline(object): def process_item(self, item, spider): print(item) return item
settiings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 OPR/67.0.3575.137 (Edition B2)' ROBOTSTXT_OBEY = False LOG_LEVEL='ERROR' ITEM_PIPELINES = { 'boss.pipelines.BossPipeline': 300, }
提升scrapy的爬取效率
增加并发:默认scrapy开启的并发线程为32个,可以适当进行增加。在settings配置文件中修改CONCURRENT_REQUESTS = 100值为100,并发设置成了为100。
降低日志级别:在运行scrapy时,会有大量日志信息的输出,为了减少CPU的使用率。可以设置log输出信息为INFO或者ERROR即可。在配置文件中编写:LOG_LEVEL = ‘INFO’
禁止cookie:如果不是真的需要cookie,则在scrapy爬取数据时可以禁止cookie从而减少CPU的使用率,提升爬取效率。在配置文件中编写:COOKIES_ENABLED = False
禁止重试:对失败的HTTP进行重新请求(重试)会减慢爬取速度,因此可以禁止重试。在配置文件中编写:RETRY_ENABLED = False
减少下载超时:如果对一个非常慢的链接进行爬取,减少下载超时可以能让卡住的链接快速被放弃,从而提升效率。在配置文件中进行编写:DOWNLOAD_TIMEOUT = 10 超时时间为10s
基于文件下载的管道类
在scrapy中我们之前爬取的都是基于字符串类型的数据,那么要是基于图片数据的爬取,那又该如何呢?
其实在scrapy中已经为我们封装好了一个专门基于图片请求和持久化存储的管道类ImagesPipeline,那也就是说如果想要基于scrapy实现图片数据的爬取,则可以直接使用该管道类即可。
基于 scrapy 爬取字符串类型的数据和爬取图片类型的数据区别?
字符串:只需要基于 xpath 进行解析且提交管道进行持久化存储
图片:小path解析出图片 src 的属性值。单独的对图片地址发起请求获取图片二进制类型的数据
ImagesPipeline使用流程
1、数据解析(图片的地址)
2、将存储图片地址的item提交到指定的管道类
3、在管道文件中定制一个机遇ImgesPigeLine的一个管道类
3.1、get_media_requset
3.2、file_path
3.3、item_completed
4、修改配置文件
4.1、添加指定图片存储的目录:IMAGES_STORE = ‘./pic’
4.2、指定开启的管道:自定制的管道类
在配置文件中进行如下配置:
案例:爬取站长素材中的图片信息(注意:伪标签)
zzsucai.py
import scrapy from zzsucai.items import ZzsucaiItem class ZzsucaiproSpider(scrapy.Spider): name = 'zzsucaiPro' # allowed_domains = ['www.xxx.com'] start_urls = ['http://sc.chinaz.com/tupian/'] def parse(self, response): div_list = response.xpath('//*[@id="container"]/div') for div in div_list: div_url = div.xpath('./div/a/img/@src2').extract_first() item = ZzsucaiItem() item['div_url'] = div_url yield item
items.py
import scrapy class ZzsucaiItem(scrapy.Item): # define the fields for your item here like: div_url = scrapy.Field()
pipelines.py
from scrapy.pipelines.images import ImagesPipeline import scrapy class ImgPipeLine(ImagesPipeline): # 可以根据图片地址进行图片数据的请求 def get_media_requests(self, item, info): yield scrapy.Request(item['div_url']) # 指定图片存储的路径 def file_path(self, request, response=None, info=None): picname = request.url.split('/')[-1] print(picname) return picname # 返回给下一个即将被执行的管道类 def item_completed(self, results, item, info): return item
settingspy
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 OPR/67.0.3575.137 (Edition B2)' # Obey robots.txt rules ROBOTSTXT_OBEY = False LOG_LEVEL='ERROR' ITEM_PIPELINES = { # 'zzsucai.pipelines.ZzsucaiPipeline': 300, 'zzsucai.pipelines.ImgPipeLine': 300, } # 指定图片存储的目录 IMAGES_STORE = './pic'
中间件
下载中间件(Downloader Middlewares) 位于scrapy引擎和下载器之间的一层组件。
作用:我们主要使用下载中间件处理请求,一般会对请求设置随机的User-Agent ,设置随机的代理。目的在于防止爬取网站的反爬虫策略。
(1)引擎将请求传递给下载器过程中, 下载中间件可以对请求进行一系列处理。比如设置请求的 User-Agent,设置代理等
(2)在下载器完成将Response传递给引擎中,下载中间件可以对响应进行一系列处理。比如进行gzip解压等。
拦截请求:
1、UA伪装:process_request
2、代理IP:process_exception:return request
拦截相应:
1、篡改相应数据,相应对象
UA池:User-Agent池
作用:尽可能多的将scrapy工程中的请求伪装成不同类型的浏览器身份。
操作流程:
1、在下载中间件中拦截请求
2、将拦截到的请求的请求头信息中的UA进行篡改伪装
3、在配置文件中开启下载中间件
UA池的封装:
user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 " "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 " "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5", "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 " "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3", "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 " "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24", "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 " "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24" ]
代理池
作用:尽可能多的将scrapy工程中的请求的IP设置成不同的。
操作流程:
1、在下载中间件中拦截请求
2、将拦截到的请求的IP修改成某一代理IP
3、在配置文件中开启下载中间件
代码演示:
middle.py
import scrapy class MiddleSpider(scrapy.Spider): name = 'middle' # allowed_domains = ['www.baidu.com'] start_urls = ['http://www.baidu.com/baidu?wd=ip&tn=54002054_dg&ie=utf-8'] def parse(self, response): res_text = response.text with open('./baidu_ip.html','w',encoding='utf-8') as f: f.write(res_text)
middlewares.py
class FirstbloodDownloaderMiddleware(object): # UA池 user_agent_list = [ "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 " "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1", } # 可被选用的代理IP PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ] PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ] # 拦截请求 def process_request(self, request, spider): # UA伪装 request.headers['User-Agent'] = random.choice(self.user_agent_list) # 验证代理操作是否生效 request.meta['proxy'] = 'http://117.81.159.108:8118' return None # 拦截所有的相应 def process_response(self, request, response, spider): return response # 拦截发生异常的请求 def process_exception(self, request, exception, spider): # 代理操作 if request.url.split(':')[0] == 'http': request.meta['proxy'] = 'http://' + random.choice(self.PROXY_http) elif request.url.split(':')[0] == 'https': request.meta['proxy'] = 'https://' + random.choice(self.PROXY_https) # 将修正之后的请求对象重新进行发送请求 return reques
settings.py
# 是否遵从 robots 协议 ROBOTSTXT_OBEY = False USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 OPR/67.0.3575.137 (Edition B2)' # 日志显示级别 LOG_LEVEL = 'ERROR' # Enable or disable downloader middlewares DOWNLOADER_MIDDLEWARES = { 'firstBlood.middlewares.FirstbloodDownloaderMiddleware': 543, }
scrapy中selenium的应用
引入
在通过scrapy框架进行某些网站数据爬取的时候,往往会碰到页面动态数据加载的情况发生,如果直接使用scrapy对其url发请求,是绝对获取不到那部分动态加载出来的数据值。但是通过观察我们会发现,通过浏览器进行url请求发送则会加载出对应的动态加载出的数据。那么如果我们想要在scrapy也获取动态加载出的数据,则必须使用selenium创建浏览器对象,然后通过该浏览器对象进行请求发送,获取动态加载的数据值。
案例分析:
需求:爬取网易新闻的国内板块下的新闻数据
需求分析:当点击国内超链进入国内对应的页面时,会发现当前页面展示的新闻数据是被动态加载出来的,如果直接通过程序对url进行请求,是获取不到动态加载出的新闻数据的。则就需要我们使用selenium实例化一个浏览器对象,在该对象中进行url的请求,获取动态加载的新闻数据。
selenium在scrapy中使用的原理分析:
当引擎将国内板块url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的新闻数据的。要想获取动态加载的新闻数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的新闻数据,然后将被篡改的response对象最终交给Spiders进行解析操作。
selenium在scrapy中的使用流程:
1、重写爬虫文件的构造方法,在该方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)
2、重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象。该方法是在爬虫结束时被调用
3、重写下载中间件的process_response方法,让该方法对响应对象进行拦截,并篡改response中存储的页面数据
4、在配置文件中开启下载中间件
代码示例:
wamgyiPro.py
import scrapy from selenium import webdriver from wangyi.items import WangyiItem class WangyiproSpider(scrapy.Spider): name = 'wangyiPro' # allowed_domains = ['www.news.163.com/domestic/'] start_urls = ['https://news.163.com'] models_url = [] def __init__(self): self.dir = webdriver.Chrome(executable_path='./wangyi/chromedriver.exe') def parse(self, response): li_num_list = [3,4,6,7,8] li_list = response.xpath('//*[@id="index2016_wrap"]/div[1]/div[2]/div[2]/div[2]/div[2]/div/ul/li') # print(li_list) for li_num in li_num_list: li_url = li_list[li_num].xpath('./a/@href').extract_first() li_title = li_list[li_num].xpath('./a/text()').extract_first() self.models_url.append(li_url) for url in self.models_url: yield scrapy.Request(url=url,callback=self.parse_model) def parse_model(self,response): parse_div_list = response.xpath('/html/body/div/div[3]/div[4]/div[1]/div/div/ul/li/div/div') for parse_div in parse_div_list: title_name = parse_div.xpath('.//div[@class="news_title"]/h3/a/text()').extract_first() new_info_url = parse_div.xpath('.//div[@class="news_title"]/h3/a/@href').extract_first() item = WangyiItem() item['title_name'] = title_name # 对新闻详情页的 url 发起请求 yield scrapy.Request(url=new_info_url,callback=self.page_info,meta={'item':item}) # 解析新闻详情页 def page_info(self,response): item = response.meta['item'] info = response.xpath('//*[@id="endText"]//text()').extract() info = ''.join(info) item['info'] = info yield item def closed(self,spider): self.dir.close()
middlewares.py
from scrapy import signals from scrapy.http import HtmlResponse from time import sleep class WangyiDownloaderMiddleware(object): def process_request(self, request, spider): return None def process_response(self, request, response, spider): # 获取在爬虫类中定义的浏览器对象 dir = spider.dir # 挑选出指定的相应对象进行篡改 # 通过 url 执行 request # 通过 requset 指定 response if request.url in spider.models_url: dir.get(request.url) sleep(1) # 包含了动态加载的新闻数据 page_text = dir.page_source # response 五大板块对应的相应对象 # 针对定位到的这些 response 进行篡改 # 如何获取动态加载歘的新闻数据? new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request) return new_response else: return response def process_exception(self, request, exception, spider): pass
items.py
import scrapy class WangyiItem(scrapy.Item): # define the fields for your item here like: title_name = scrapy.Field() info = scrapy.Field() pass
pipelines.py
class WangyiPipeline(object): def process_item(self, item, spider): print(item) return item
settings.py
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36 OPR/67.0.3575.137 (Edition B2)' ROBOTSTXT_OBEY = False LOG_LEVEL = 'ERROR' DOWNLOADER_MIDDLEWARES = { 'wangyi.middlewares.WangyiDownloaderMiddleware': 543, } ITEM_PIPELINES = { 'wangyi.pipelines.WangyiPipeline': 300, }