爬虫之scrapy框架
一、认识scrapy框架
何为框架,就相当于一个封装了很多功能的结构体,它帮我们把主要的结构给搭建好了,我们只需往骨架里添加内容就行。scrapy框架是一个为了爬取网站数据,提取数据的框架,我们熟知爬虫总共有四大部分,请求、响应、解析、存储,scrapy框架都已经搭建好了。scrapy是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架,scrapy使用了一种非阻塞的代码实现并发的,结构如下:
1、引擎(EGINE) 引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。有关详细信息,请参见上面的数据流部分。 2、调度器(SCHEDULER) 用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址 3、下载器(DOWLOADER) 用于下载网页内容, 并将网页内容返回给EGINE,下载器是建立在twisted这个高效的异步模型上的 4、爬虫(SPIDERS) SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求 5、项目管道(ITEM PIPLINES) 在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作 下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间,主要用来处理从EGINE传到DOWLOADER的请求request,已经从DOWNLOADER传到EGINE的响应response, 你可用该中间件做以下几件事: (1) process a request just before it is sent to the Downloader (i.e. right before Scrapy sends the request to the website); (2) change received response before passing it to a spider; (3) send a new Request instead of passing received response to a spider; (4) pass response to a spider without fetching a web page; (5) silently drop some requests. 6、爬虫中间件(Spider Middlewares) 位于EGINE和SPIDERS之间,主要工作是处理SPIDERS的输入(即responses)和输出(即requests)
1,模块下载
Windows: a. pip3 install wheel b. 下载twisted http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted c. 进入下载目录,执行 pip3 install Twisted‑17.1.0‑cp35‑cp35m‑win_amd64.whl d. pip3 install pywin32 e. pip3 install scrapy
切记按照从上往下的顺序执行
2,开启一个scrapy项目
1,新建一个项目 在pycharm的终端里输入:scrapy startproject 项目名称 构建了一个如下的文件目录: project_name/ scrapy.cfg: project_name/ __init__.py items.py pipelines.py settings.py spiders/ __init__.py scrapy.cfg 项目的主配置信息。(真正爬虫相关的配置信息在settings.py文件中) items.py 设置数据存储模板,用于结构化数据,如:Django的Model pipelines 数据持久化处理 settings.py 配置文件,如:递归的层数、并发数,延迟下载等 spiders 爬虫目录,如:创建文件,编写爬虫解析规则 2,新建一个爬虫程序 也是在pycharm的终端下输入:cd 项目名称 #进入项目目录下 再输入:scrapy genspider 爬虫程序名称 爬虫程序的起始url 此时就会在第二层的project_name文件夹下创建一个spider文件夹,spider文件夹下就会有一个‘爬虫应用程序名字.py的文件’,如下: project_name: project_name: spider: app_name.py
此时app_name.py:
import scrapy class QiubaiSpider(scrapy.Spider): name = 'qiubai' #应用名称 #允许爬取的域名(如果遇到非该域名的url则爬取不到数据) allowed_domains = ['https://www.qiushibaike.com/'] #我们一般情况下都会把给注释掉, #起始爬取的url start_urls = ['https://www.qiushibaike.com/'] #访问起始URL并获取结果后的回调函数,该函数的response参数就是向起始的url发送请求后,获取的响应对象.该函数返回值必须为可迭代对象或者NUll def parse(self, response): print(response.text) #获取字符串类型的响应内容 print(response.body)#获取字节类型的相应内容
3,修改settings.py配置文件
修改内容及其结果如下: 19行:USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36' #伪装请求载体身份 22行:ROBOTSTXT_OBEY = False #可以忽略或者不遵守robots协议
4,运行爬虫程序
在pycharm终端里输入:scrapy crawl 爬虫程序名称 #这样就执行爬虫程序了,这种情况下是要打印日志的 scrapy crawl 爬虫程序名称 --nolog #这样就不会打印日志 还有一种方式:在最外层的project_name文件下新建一个start.py文件,名字随便写,在文件里写入: from scrapy.cmdline import execute execute(['scrapy', 'crawl', '爬虫程序名字','--nolog'])
从此以后,我们每次只需要运行start文件,就可以让程序跑起来
二、请求、响应、解析
这三个功能的实现主要就是在爬虫程序下,我这里以爬取网易新闻为例子。
1,项目要求
我此次项目的功能是:爬取网易新闻下的国内、国际、军事、航空的的所有新闻。
第一步:
第二步:点击进入四个板块
第三步:点击每条新闻,拿到每条新闻的标题,url,图片的url,所属的板块,关键字,内容
2,新建项目
在终端里依次输入: scrapy startproject WY #Demo是我的项目名称 cd WY #进入我的项目环境下 scrapy genspider wangyi news.163.com #创建我的网易爬虫程序
3,编写wangyi.py代码
import scrapy class WangyiSpider(scrapy.Spider): name = 'wangyi' # allowed_domains = ['wangyi.com'] start_urls = ['https://news.163.com/'] #这是爬取网页的第一个url,然后回调parse函数,并把返回值给回调函数
def parse(self, response): #接收到第一次请求的响应 a_list=response.xpath('//div[@class="index_head"]//li/a') #利用xpath进行数据解析 l1=[3,4,6,7] for i in l1: link=a_list[i].xpath('./@href').extract_first() #这是拿到每个板块的url genre=a_list[i].xpath('./text()').extract_first() #这是拿到了每个板块的名字 request=scrapy.Request(url=link,callback=self.parse_one) #这是向每个板块发送请求,然后把响应给回调函数parse_one yield request def parse_one(self,response): #拿到板块页面响应 div_list=response.xpath('//div[@class="ndi_main"]/div') #这是拿到每条新闻的div标签
for div in div_list: #循环所有的新闻 url=div.xpath('.//a[@class="na_pic"]/@href').extract_first() #拿到每条新闻的url title=div.xpath('.//a[@class="na_pic"]/img/@alt').extract_first() #拿到每条新闻的title img_url=div.xpath('.//a[@class="na_pic"]/img/@src').extract_first() #拿到每条新闻的图片的url key_list=div.xpath('.//div[@class="keywords"]//text()').extract() #拿到每条新闻的关键字列表 keyword_list=[] for key in range(len(key_list)): if (key+1) % 2 ==0: keyword_list.append(key_list[key]) keywords=','.join(keyword_list) #这是构建了每条新闻的关键字 request = scrapy.Request(url=url, callback=self.parse_two) #这是向没条新闻的url发送请求,把请求响应给parse_two回调函数 yield request def parse_two(self,response): content_list=response.xpath('//div[@id="endText"]/p//text()').extract() #这是新闻内容,列表形式 content='' for value in content_list: content+=value.strip() #构建每条新闻的内容
以上的代码就能拿到想要的数据,但是哈,我在打印每条新闻时,好像并没有数据,这是咋回事呢,仔细检查代码,可以确定是每个板块的请求是发出去了,parse_one也是接收到响应的,但好像响应内容并不全面,于是我猜测应该是页面加载的问题,当我们给每个板块发送请求后,马上拿到的并不是页面的所有内容,有些js代码还没执行。对于这种问题,在我们之前的爬虫过程也遇到了,可以通过selenium模块来解决。
三、selenium模块在scrapy框架的实现
在爬虫过程中,对于动态加载的页面,我们可以使用selenium模块来解决,实例化一个浏览器对象,然后控制浏览器发送请求,等待页面内容加载完毕后,再获取页面信息。
1,selenium模块在scrapy框架中实现原理
当引擎将国内板块url对应的请求提交给下载器后,下载器进行网页数据的下载,然后将下载到的页面数据,封装到response中,提交给引擎,引擎将response在转交给Spiders。Spiders接受到的response对象中存储的页面数据里是没有动态加载的新闻数据的。要想获取动态加载的新闻数据,则需要在下载中间件中对下载器提交给引擎的response响应对象进行拦截,切对其内部存储的页面数据进行篡改,修改成携带了动态加载出的新闻数据,然后将被篡改的response对象最终交给Spiders进行解析操作
2,selenium的使用流程
重写爬虫文件的构造方法,在该方法中使用selenium实例化一个浏览器对象(因为浏览器对象只需要被实例化一次)
重写爬虫文件的closed(self,spider)方法,在其内部关闭浏览器对象。该方法是在爬虫结束时被调用
重写下载中间件的process_response方法,让该方法对响应对象进行拦截,并篡改response中存储的页面数据
在配置文件中开启下载中间件
3,代码实现
3.1 wangyi.py
from selenium import webdriver
class WangyiSpider(scrapy.Spider):
def __init__(self): #这是构造方法中,实例化一个浏览器对象
self.driver=webdriver.Chrome(r'E:\Google\Chrome\Application\chromedriver.exe')
def close(self,spider): #这个方法会在整个程序结束时执行,从而把浏览器对象给关闭
self.driver.close()
在上面的wangyi.py文件中加入这些代码就行
3.2 拦截响应,并篡改响应,在中间件中实现,middlewares.py
from scrapy.http import HtmlResponse class WyDownloaderMiddleware(object):
#不管是什么请求,返回的响应对象都会经过这个函数 def process_response(self, request, response, spider):
#request:响应对象对应的请求对象,reqeust.url就可以拿到本次请求的路径
#response:拦截的响应对象
#spider:爬虫文件中对应的爬虫类的实例,spider.driver就可以拿到实例化的浏览器对象
#这是做了一个要用浏览器对象发送请求的白名单列表 allow_list=['http://news.163.com/domestic/','http://news.163.com/world/','http://war.163.com/','http://news.163.com/air/']
#如果,这次请求的路径在白名单中,就把响应给拦截,然后用浏览器对象重新发送这个请求,然后拿到页面信息,再把拿到页面信息封装到一个响应对象里,然后才返回 if request.url in allow_list:
#这是一个隐式等待 spider.driver.implicitly_wait(10) spider.driver.get(request.url)
#这是利用浏览器对象实现一个把滚动条拿到最下面的js过程 js = 'window.scrollTo(0,document.body.scrollHeight)' spider.driver.execute_script(js)
#获取当前页面上的内容 res=spider.driver.page_source
#把页面上的内容封装到一个响应体对象里,并返回 return HtmlResponse(url=request.url,body=res,encoding='utf_8',request=request)
#如果本次请求不在白名单上,就不用拦截,不做任何处理 return response
4,完成setting.py文件的修改
#放开中间件,让中间件生效
DOWNLOADER_MIDDLEWARES = { 'wangyiPro.middlewares.WangyiproDownloaderMiddleware': 543, }
四、数据存储
1,基于终端指令的持久化存储
保证爬虫文件的parse方法中有可迭代类型对象(通常为列表or字典)的返回,该返回值可以通过终端指令的形式写入指定格式的文件中进行持久化操作 执行输出指定格式进行存储:将爬取到的数据写入不同格式的文件中进行存储 scrapy crawl 爬虫名称 -o xxx.json scrapy crawl 爬虫名称 -o xxx.xml scrapy crawl 爬虫名称 -o xxx.csv
2,基于管道的持久化存储
基于管道的持久化存储,主要依靠scrapy框架的item.py和pipelines.py文件
item.py:数据结构模板文件,定义数据属性
pipelines.py:管道文件,接收数据(item),进行持久化操作
整个流程:
1,爬虫文件爬取到数据后,把数据赋给item对象
2,使用yield关键字将item对象提交给pipelines管道
3,在管道文件中的process_item方法接收item对象,然后把item对象存储
4,在setting中开启管道
2.1 完成item.py文件的书写,也就是定义数据属性
import scrapy class WyItem(scrapy.Item): genre=scrapy.Field() #所属板块 title=scrapy.Field() #标题 url=scrapy.Field() #链接 img_url=scrapy.Field() #图片链接 keywords=scrapy.Field() #关键字 content=scrapy.Field() #全部内容
对于本次爬取的网易新闻我只存储这6个信息
2.2 完善wangyi.spider爬虫程序
import scrapy from WY.items import WyItem from selenium import webdriverclass WangyiSpider(scrapy.Spider): name = 'wangyi' start_urls = ['https://news.163.com/'] def __init__(self): self.driver=webdriver.Chrome(r'E:\Google\Chrome\Application\chromedriver.exe') def parse(self, response): a_list=response.xpath('//div[@class="index_head"]//li/a') l1=[3,4,6,7] for i in l1: link=a_list[i].xpath('./@href').extract_first()
#在这实例化item对象 item=WyItem() genre=a_list[i].xpath('./text()').extract_first()
#把板块名字赋给item对象 item['genre']=genre
#在发送请求的同时,完成一个请求传参,也就是把item对象传递给回调函数,在回调函数也可以使用item对象‘meta={'item':item}’ request=scrapy.Request(url=link,callback=self.parse_one,meta={'item':item}) yield request def parse_one(self,response): div_list=response.xpath('//div[@class="ndi_main"]/div')
#取出item对象 item=response.meta['item'] for div in div_list: url=div.xpath('.//a[@class="na_pic"]/@href').extract_first() title=div.xpath('.//a[@class="na_pic"]/img/@alt').extract_first() img_url=div.xpath('.//a[@class="na_pic"]/img/@src').extract_first() key_list=div.xpath('.//div[@class="keywords"]//text()').extract() keyword_list=[] for key in range(len(key_list)): if (key+1) % 2 ==0: keyword_list.append(key_list[key]) keywords=','.join(keyword_list)
#把拿到的数据赋给item对象 item['url']=url item['title']=title item['img_url']=img_url item['keywords']=keywords
#在这又把item对象传递给了下一个回调函数 request = scrapy.Request(url=url, callback=self.parse_two, meta={'item': item}) yield request def parse_two(self,response): item=response.meta['item'] content_list=response.xpath('//div[@id="endText"]/p//text()').extract() content='' for value in content_list: content+=value.strip() item['content']=content yield item def close(self,spider): self.driver.close()
注意:回调函数的返回值为一个请求对象时,会把响应给回调函数,从而执行对应的回调函数;但当回调函数的返回值为item对象时,它会执行到pipelines.py文件,
并把item对象传给process_item方法,并执行这方法
2.3 pipelines.py文件代码的实现,也就是真正的存储过程
1)存储在文件中
class WyFilePipeline(object): #构造方法 def __init__(self): self.fp = None #定义一个文件描述符属性 #下列都是在重写父类的方法: #开始爬虫时,执行一次 def open_spider(self,spider): print('爬虫开始') self.fp = open('./data.txt', 'w') #因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。 def process_item(self, item, spider): #将爬虫程序提交的item进行持久化存储 self.fp.write(item['author'] + ':' + item['content'] + '\n') return item #结束爬虫时,执行一次 def close_spider(self,spider): self.fp.close() print('爬虫结束')
setting配置
#开启管道
ITEM_PIPELINES = {
'WY.pipelines.WyFilePipeline': 300,
}
2)存储在mongodb数据库中
import pymongo class WyMongodbPipeline(object): def open_spider(self,spider):
#连接mongodb数据库 self.client = pymongo.MongoClient(host='localhost', port=27017)
#进入wangyi库 self.db = self.client.wangyi def process_item(self, item, spider): if dict(item):
#item对象转换成字典,存入wangyi库下的xinwen集合 self.db.xinwen.save(dict(item))return item def close_spider(self,spider):
#断开连接 self.client.close()
setting配置
#开启管道
ITEM_PIPELINES = {
'WY.pipelines.WyMongodbPipeline': 300,
}
3)存储在mysql数据库中
import pymysql class WyMysqlPipeline(object): conn = None #mysql的连接对象声明 cursor = None#mysql游标对象声明 def open_spider(self,spider): print('开始爬虫') #链接数据库 self.conn = pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='123456',db='wangyi') #编写向数据库中存储数据的相关代码 def process_item(self, item, spider): #1.链接数据库 #2.执行sql语句 sql = 'insert into xinwen values("%s","%s")'%(item['genre'],item['url']....) self.cursor = self.conn.cursor() #执行事务 try: self.cursor.execute(sql) self.conn.commit() except Exception as e: print(e) self.conn.rollback() return item def close_spider(self,spider): print('爬虫结束') self.cursor.close() self.conn.close()
setting配置
ITEM_PIPELINES = { 'WY.pipelines.WyMysqlPipeline': 300, }
4)存储在redis数据库
import redis class WyRedisPipeline(object): conn = None def open_spider(self,spider): print('开始爬虫') #创建链接对象 self.conn = redis.Redis(host='127.0.0.1',port=6379) def process_item(self, item, spider): dict = { 'genre':item['genre'], 'content':item['content']....... } #写入redis中 self.conn.lpush('data', dict) return item
setting配置
ITEM_PIPELINES = { 'WY.pipelines.WyRedisPipeline': 300, }
可以从上面四种存储方式看出,模式都是一样的,主要是你的类名要和你的setting里的要一致
5)可以同时存储在多个里面,以同时存储在文件和mongodb中为例
class WyFilePipeline(object): #构造方法 def __init__(self): self.fp = None #定义一个文件描述符属性 #下列都是在重写父类的方法: #开始爬虫时,执行一次 def open_spider(self,spider): print('爬虫开始') self.fp = open('./data.txt', 'w') #因为该方法会被执行调用多次,所以文件的开启和关闭操作写在了另外两个只会各自执行一次的方法中。 def process_item(self, item, spider): #将爬虫程序提交的item进行持久化存储 self.fp.write(item['author'] + ':' + item['content'] + '\n') return item #为什么要在这返回item,也就是给下一种存储方法使用 #结束爬虫时,执行一次 def close_spider(self,spider): self.fp.close() print('爬虫结束')
import pymongo
class WyMongodbPipeline(object):
def open_spider(self,spider):
#连接mongodb数据库
self.client = pymongo.MongoClient(host='localhost', port=27017)
#进入wangyi库
self.db = self.client.wangyi
def process_item(self, item, spider):
if dict(item):
#item对象转换成字典,存入wangyi库下的xinwen集合
self.db.xinwen.save(dict(item))
return item
def close_spider(self,spider):
#断开连接
self.client.close()
setting配置
ITEM_PIPELINES = { 'WY.pipelines.WyFilePipeline': 300, 'WY.pipelines.WyMongodbPipeline': 290, }
后面的数字决定执行优先级,
五、UA池和IP代理池
我们都知道哈,我们要爬取网页的时候,门户网站会有很多反爬策略,比如检查UA和IP,为了绕过这层反爬,我们可以使用UA池和IP池来解决。改变我们的ua和ip是在发送请求前要做的,而且我们要给每个请求都伪装一下,所以我可以在中间件的process_request方法中添加。利用UA池和IP池就会使得每次请求的UA和ip在很大程度上不一样,就使得被反爬的几率变小
1,UA池
middlewares.py文件中添加一个UA类
from scrapy.downloadermiddlewares.useragent import UserAgentMiddleware import random class MyUserAgentMiddleWare(UserAgentMiddleware): def process_request(self, request, spider):
#这是一个可用的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" ]
#从可用UA列表随机取出一个UA ua=random.choice(user_agent_list)
#封装到请求的headers request.headers.setdefault('User-Agent',ua) return None
setting配置
DOWNLOADER_MIDDLEWARES = { 'WY.middlewares.WyDownloaderMiddleware': 543, 'WY.middlewares.MyUserAgentMiddleWare': 542, }
把刚才写的UA中间件添加进去
2,IP代理池
在middlewares.py文件中添加一个IP类
class MyIPMiddleWare(object): def process_request(self, request, spider):
#这是可用的ip可以从网上的什么快代理、goubanjia等网站获取
#这是http请求类型的可用IP PROXY_http = [ '153.180.102.104:80', '195.208.131.189:56055', ]
#这是https请求类型的可用IP PROXY_https = [ '120.83.49.90:9000', '95.189.112.214:35508', ]
#取出本次请求路径的协议 head=request.url.split(':')[0]
#如果是https的请求,就从https的列表中随机取出ip if head == 'https': proxy=random.choice(PROXY_https) #否则就从http的列表中随机取出ip else: proxy = random.choice(PROXY_http)
#封装到请求体中 request.meta['proxy'] = head + '://' + proxy return None
setting配置
DOWNLOADER_MIDDLEWARES = { 'WY.middlewares.WyDownloaderMiddleware': 543, 'WY.middlewares.MyUserAgentMiddleWare': 542, 'WY.middlewares.MyIPMiddleWare': 541, }
六、发送post请求
之前我发送的第一个请求都是写在start_urls列表,让它自动帮我们发送第一个请求,其实我可以手动发送第一个请求。scrapy框架是调用了Spider类下面的一个start_requests方法发送第一个请求,所以我可以重写这个方法,自己手动发送第一个请求,它默认是发送的是get请求,我们可以把它换成post请求。
def start_requests(self): #请求的url post_url = 'http://fanyi.baidu.com/sug' # post请求参数 formdata = { 'kw': 'wolf', } # 发送post请求 yield scrapy.FormRequest(url=post_url, formdata=formdata, callback=self.parse) #在这里我们自己回调了parse函数
七、递归解析
1,情况分析
很多情况下,我们爬取的数据不止一个,他们会以索引的方式存在于页末,比如下一页等,但是这些页面数据的结构都是一样的,所以用的解析方式也是一样的。对于这样的爬虫,我们可以使用递归解析完成。
实现流程:
1,访问第一页,拿到响应,交给parse解析出第一页的数据,存储。
2,但第一页中肯定会拿到下一页的链接,我们在parse中对下一页的链接发起请求,然后这次请求的回调函数也是当前所在的parse,在自己函数中调用自己,这就形成了递归,递归函数必须要有一个出口,不然就行成了死循环,我们的出口就是,当下一页的链接不存在时,就不要发送请求了。
2,代码实现
spider程序
import scrapy from Demo1.items import Demo1Item class AmazonSpider(scrapy.Spider): name = 'Amazon' # allowed_domains = ['amazon.cn'] # start_urls = ['http://amazon.cn/'] def start_requests(self): request=scrapy.Request('https://www.amazon.cn/s/ref=nb_sb_noss/462-2826076-8570112?__mk_zh_CN=%E4%BA%9A%E9%A9%AC%E9%80%8A%E7%BD%91%E7%AB%99&url=search-alias%3Daps&field-keywords=%E6%89%8B%E6%9C%BA',
callback=self.parse_index) yield request def parse_index(self, response): links=response.xpath('//ul[@id="s-results-list-atf"]/li/div/div[2]/div/div/a/@href').extract() for link in links: req=scrapy.Request(url=link,callback=self.parse_detail) yield req next_page_link='http://amazon.cn'+response.xpath('//a[@id="pagnNextLink"]/@href').extract_first() #这是拿到下一个的链接
#判断链接是否存在,也就是递归的出口 if next_page_link: request=scrapy.Request(url=next_page_link,callback=self.parse_index) #当链接存在时,发送请求,并回调自己,形成递归 yield request def parse_detail(self,response): title=response.xpath('//span[@id="productTitle"]/text()')[0].extract().strip() price=response.xpath('//span[@id="priceblock_ourprice"]/text()')[0].extract().strip() delivery=''.join(response.xpath('//span[@id="ddmMerchantMessage"]//text()').extract()) item=Demo1Item() item['title']=title item['price']=price item['delivery']=delivery yield item
八、CrawlSpider
1,简介
Crawlspider其实是Spider的一个子类,除了继承到Spider的特性和功能外,还派生出其他强大的功能,其中最显著的就是‘LinkExtractors链接提取器’。爬取网页上的链接继续发送请求时使用CrawlSpider更合适
2,创建CrawlSpider爬虫程序
1,新建一个项目,这个和spider一样的 scrapy startproject 项目名称 2,创建一个CrawlSpider的爬虫程序 scrapy genspider -t crawl 程序名字 url #这个比spider多了-t crawl,表示基于CrawlSpider类的
得到如下的程序:
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class ChoutidemoSpider(CrawlSpider): name = 'choutiDemo' #allowed_domains = ['www.chouti.com'] start_urls = ['http://www.chouti.com/'] #这是提取规则 rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) def parse_item(self, response): i = {} #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() #i['name'] = response.xpath('//div[@id="name"]').extract() #i['description'] = response.xpath('//div[@id="description"]').extract() return i 这个程序和spider最大的不同在于,CrawlSpider类多了一个rules属性,作用是定义‘提取动作’,在rules中可以包含一个或多个rule对象,在rule对象中包含了LingkExtractor对象
3,LinkExtractor,链接提取器
LinkExtractor( allow=r'Items/',# 满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。 deny=xxx, # 满足正则表达式的则不会被提取。 restrict_xpaths=xxx, # 满足xpath表达式的值会被提取 restrict_css=xxx, # 满足css表达式的值会被提取 deny_domains=xxx, # 不会被提取的链接的domains。 ) 作用:提取response中符合规则的链接
4,Rule,规则解析器
根据链接提取器中提取到的链接,根据指定规则提取解析器链接网页中的内容。 Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True) 参数介绍: 参数1:指定链接提取器 参数2:指定规则解析器解析数据的规则(回调函数) 参数3:是否将链接提取器继续作用到链接提取器提取出的链接网页中。当callback为None,参数3的默认值为true。
5,爬取的流程
a)爬虫文件首先根据起始url,获取该url的网页内容
b)链接提取器会根据指定提取规则将步骤a中网页内容中的链接进行提取
c)规则解析器会根据指定解析规则将链接提取器中提取到的链接中的网页内容根据指定的规则进行解析
d)将解析数据封装到item中,然后提交给管道进行持久化存储
6,实例
import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class CrawldemoSpider(CrawlSpider): name = 'qiubai' #allowed_domains = ['www.qiushibaike.com'] start_urls = ['https://www.qiushibaike.com/pic/'] #连接提取器:会去起始url响应回来的页面中提取指定的url link = LinkExtractor(allow=r'/pic/page/\d+\?') #s=为随机数 link1 = LinkExtractor(allow=r'/pic/$')#爬取第一页 #rules元组中存放的是不同的规则解析器(封装好了某种解析规则) rules = ( #规则解析器:可以将连接提取器提取到的所有连接表示的页面进行指定规则(回调函数)的解析 Rule(link, callback='parse_item', follow=True), Rule(link1, callback='parse_item', follow=True), ) #这里拿到的就是提取出来的链接请求返回的响应体 def parse_item(self, response): print(response)