专业爬虫框架 -- scrapy初识及基本应用

scrapy基本介绍

Scrapy一个开源和协作的框架,其最初是为了页面抓取 (更确切来说, 网络抓取 )所设计的,使用它可以以快速、简单、可扩展的方式从网站中提取所需的数据。

但目前Scrapy的用途十分广泛,可用于如数据挖掘、监测和自动化测试等领域,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services ) 或者通用的网络爬虫。Scrapy 是基于twisted框架开发而来,twisted是一个流行的事件驱动的python网络框架。

因此Scrapy使用了一种非阻塞(又名异步)的代码来实现并发。

 Scrapy架构

 百度上找到的Scrapy架构图:

1、引擎(Engine):
引擎负责控制系统所有组件之间的数据流,并在某些动作发生时触发事件。

有关详细信息,请参见上面的数据流部分。

------>>>

2、调度器(Scheduler):
用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返回. 可以想像成一个URL的优先级队列, 由它来决定下一个要抓取的网址是什么, 同时去除重复的网址

------>>>

3、下载器(Dowloader):
用于下载网页内容, 并将网页内容返回给Engine,下载器是建立在twisted这个高效的异步模型上的

------>>>

4、爬虫(Spiders):
SPIDERS是开发人员自定义的类,用来解析responses,并且提取items,或者发送新的请求

------>>>

5、项目管道(Item Pipelines):
在items被提取后负责处理它们,主要包括清理、验证、持久化(比如存到数据库)等操作
下载器中间件(Downloader Middlewares)位于Scrapy引擎和下载器之间,主要用来处理从Engine传到Downloader的请求request,已经从Downloader传到Engine的响应response。

------>>>

6、爬虫中间件(Spider Middlewares):
位于Engine和SPIDERS之间,主要工作是处理Spiders的输入(即responses)和输出(即requests)

 Scrapy安装

windows安装命令:pip3 install scrapy

依赖项安装:

pip3 install lxml

pip3 install whee

pip3 install pyopenssl

依赖项如果已经安装的可以跳过

官网链接:ghttps://docs.scrapy.org/en/latest/topics/commands.htmlscrapy官网链接:ghttps://docs.scrapy.org/en/latest/topics/commands.html

scrapy框架使用及命令详解

常用命令

查看帮助

 scrapy -hscrapy <command> -h

 有两种命令:其中Project-only必须切到项目文件夹下才能执行,而Global的命令则不需要

  Global commands

startproject #创建项目
genspider    #创建爬虫程序
settings     #如果是在项目目录下,则得到的是该项目的配置
runspider    #运行一个独立的python文件,不必创建项目
shell        #scrapy shell url地址  在交互式调试,如选择器规则正确与否
fetch        #独立于程单纯地爬取一个页面,可以拿到请求头
view         #下载完毕后直接弹出浏览器,以此可以分辨出哪些数据是ajax请求
version      #scrapy version 查看scrapy的版本,scrapy version -v查看scrapy依赖库的版本

Project-only commands

crawl        #运行爬虫,必须创建项目才行,确保配置文件中ROBOTSTXT_OBEY = False
check        #检测项目中有无语法错误
list         #列出项目中所包含的爬虫名
edit         #编辑器,一般不用
parse        #scrapy parse url地址 --callback 回调函数  #以此可以验证我们的回调函数是否正确
bench        #scrapy bentch压力测试

对于爬虫而言,我们需要关心及常用的命令就三个:

startproject创建项目、 genspider创建爬虫程序、crawl启动爬虫

创建项目

手动新建一个“day23”的文件夹,进入Teminal终端

scrapy startproject Newspro  #Newspro是项目名称

回车执行后就会自动帮我“day23”的文件夹下创建Newspro

ps:这里项目名称如果写成News pro,day23下父级目录会叫pro,然后是子级目录News

文件目录:

文件说明:

● scrapy.cfg:项目的主配置信息,用来部署scrapy时使用,爬虫相关的配置信息在settings.py文件中。

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

创建爬虫程序

上一步Teminal终端中创建完项目之后,已经提示需要先cd Newspro,开始创建爬虫程序:

cd Newspro  #进入项目文件夹
scrapy genspider wangyi news.163.com/  #创建爬虫程序1

即告诉Teminal终端:

我要用scrapy框架,创建(genspider)一个叫"wangyi"的爬虫程序,"news.163.com/"是要爬取的url,可以省略https://

可以看到在Spiders文件夹下,就自动帮我们生成了一个"wangyi.py"的文件,并且文件中自动写好了一个类,以及一些配置参数。

当然如果是老手也可以自己手动创建模块,但是小白的话更推荐用命令创建,不然模块中少写了参数,就会导致一些bug……

另外需要注意:里面的parse方法,parse这个方法名不能改,这是框架自带的回调函数

再创建一个环球新闻网的模块:也自动生成了一个"huanqiu.py"的模块

模块中的域名默认是按照http进行拼的,如果不对,也可以手动改成https

 scrapy genspider huanqiu huanqiu.com

在做爬虫的时候,我们可能不止爬取一个网站,规范是:

将它们全部放在'Spiders'文件夹下,每一个要爬取的网站建立单独一个模块,然后在模块里完善具体的爬虫逻辑和解析逻辑。

完整的创建项目 -> 创建爬虫程序代码如下:

scrapy startproject Newspro  #Newspro是项目名称
cd Newspro                   #进入项目文件夹
scrapy genspider wangyi news.163.com  #创建爬虫程序1
scrapy genspider huanqiu huanqiu.com  #创建爬虫程序2

Spider类详解

Spiders是定义如何抓取某个站点(或一组站点)的类,包括如何执行爬行(即跟随链接)以及如何从其页面中提取结构化数据(即抓取项目)。

换句话说,Spiders是您为特定站点(或者在某些情况下,一组站点)爬网和解析页面定义自定义行为的地方。 

=====================================================================

① 生成初始的Requests来爬取第一个URLS,并且标识一个回调函数;

第一个请求定义在start_requests()方法内,默认从start_urls列表中获得url地址来生成Request请求,默认的回调函数是parse方法。回调函数在下载完成返回response时自动触发

parse不能改名,必须叫parse

------->>>

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

------->>>

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

------->>>

④ 最后,针对返回的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)

启动爬虫程序

获取网易新闻的html信息,修改添加"wangyi.py"模块中的代码:

import scrapyclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址,可以放多个#回调函数,解析方法def parse(self, response):#windos系统记得写encoding="utf8",不然写入的会是一个空文件with open("new163..html","w",encoding="utf8") as f: f.write(response.text)

启动爬虫程序方式1:Teminal在终端输入以下代码

ls
scrapy crawl wangyi   #crawl启动的意思,后面跟上要启动的模块名

回车之后,会输出很多日志,日志跑完,就会出现一个"new163"的html文件,说明执行成功

html文件可以直接用浏览器打开,就是网页新闻的页面

如果不想看到这一堆日志,可以在启动文件的时候,加上--nolog

一般刚开始调试的时候不建议关闭日志,否则哪里写错了,也看不到报错信息

scrapy crawl wangyi --nolog  #启动网易模块,且不显示日志

每次都得在终端输入命令还是有点麻烦,所以也有另一种启动方式

启动爬虫程序方式2:通过run来执行启动

scrapy框架没有自带的这个功能,我们可以创建一个py脚本文件

在项目的根目录下进行创建一个py文件,例如我的项目文件名叫"Newspro",就是在它下面创建

运行以下代码,也可以进行启动爬虫程序

from scrapy.cmdline import execute#['scrapy', 'crawl', '文件名']
execute(['scrapy', 'crawl', 'wangyi'])#需要关闭日志的话,加上"--nolog"即可
# execute(['scrapy', 'crawl', 'wangyi',"--nolog"])

项目使用--scrapy实战案例详解

基于前面创建的Newspro项目下的'wangyi.py'模块,批量爬取网易新闻首页板块tab的15个新闻分类里的所有新闻标题,如下截图圈出的板块:

-------------------------------------------------------------------------------------------------------------------------

当鼠标悬浮在tab标题上时,悬浮在不同标题会自动切换显示对应内容,说明此时非当前tab标题下的内容被隐藏了,浏览器并没有真正对服务器发起访问请求,比如说这个15个tab标题分类下,每个分类有100条新闻,那么总共就是1500条新闻,都在第一次的请求响应中。 

 通过之前爬取下来的html文件对比:

通过对比html文件就可以看出,响应信息只是放在不同位置 

⑴ 批量爬取网易新闻标题 

明确完整的爬取需求:

① tab板块中15个分类的所有新闻分类(即tab名称)

② 所有的新闻标题

③ 以及所有的新闻内容

=====================================================================

通过之前爬取下来的首页信息的html文件中可以看出,目前能拿到的是tab名称和新闻标题

新闻内容需要请求具体每个新闻的url,所以分三步来实现:

第一步:先来完成首页最容易获取的信息:解析新闻标题

第二步:解析tab分类名称

第三步:最后爬取具体的所有新闻内容,请求所有新闻的url进行获取

现在正式开始实现第一步:解析新闻标题

解析新闻标题 (错误示范)

先打开浏览器 -> f12进行元素xpath定位,先找到新闻标题对应的素: 

//div[@class="news_title"]/h3/a/text()

import scrapyclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#回调函数,解析方法def parse(self, response):#获取新闻标题的列表:scrapy框架里也支持xpath语法news_title_list = response.xpath('//div[@class="news_title"]/h3/a')print("news_title_list::",news_title_list)print("news_title_list长度:",len(news_title_list)) #长度即新闻的个数

然后在bin模块的脚本文件中启动(关闭了日志):

bin文件执行完成之后,发现wangyi.py文件的输出并没有返回预期的信息

重新开启日志,再run一下:发现有报错

在上面的网页新闻f12元素中是能够找到新闻标题的,说明定义的xapth规则是没问题的

但是为什么解析不出来数据呢?这是一个坑,极大可能性是,在f12中我们看到的数据,是js渲染的,js把数据渲染到对应的标签里了

所以解决办法是:我们要先去看之前爬取下来的html文件,找到对应的hidden隐藏的属性值

解析新闻标题 (正确示范):重新修改代码

import scrapyclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#回调函数,解析方法def parse(self, response):#获取新闻标题的列表:scrapy框架里也支持xpath语法news_title_list = response.xpath('//div[contains(@ne-if,"{{")]/div/a/text()')print("news_title_list::",news_title_list)print("news_title_list长度:",len(news_title_list)) #长度即新闻的个数

重新run一下bin文件:这次有返回信息,说明解析成功

但是框架自动帮我们用Selector封装了一个data,将新闻标题放在data中

我们只想要新闻标题,所以使用extract()进行提取:直接加在xapth后面就行

import scrapyclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#回调函数,解析方法def parse(self, response):#获取新闻标题的列表:scrapy框架里也支持xpath语法news_title_list = response.xpath('//div[contains(@ne-if,"{{")]/div/a/text()').extract()print("news_title_list::",news_title_list)print("news_title_list长度:",len(news_title_list)) #长度即新闻的个数

重新run一下bin文件:这样就提取出了text值,即所有的新闻标题

extract()还有一个方法:extract_first(),值提取第一个

总结一下两个提取方法:

extract():提取Selector对象里的所有的data的text属性值
extract_first():提取Selector对象里的data的第一个text属性值,即索引为0的text

⑵ 爬取新闻分类+标题+链接

上面爬取新闻标题时,是对这个项目的初探

但是有个问题就是:只能获取到所有的新闻标题,没有新闻tab分类

=====================================================================

所以,我们重新转换一下思路:

❶ 先建立每个tab分类名称的关系映射字典 -> 在回调函数中写解析内容的代码:

这次直接提取网页新闻首页html文件中的分类title,也就是ne-if这个属性值

由于有15个分类板块,这里先取其中6个分类,进行循环15次,只返回在字典表中存在的

import scrapyclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#建立一个tab分类名称关系映射字典:cate_mun_map = {"{{__i == 0}}" : "要闻" , "{{__i == 1}}": "上海","{{__i == 3}}" : "国内" , "{{__i == 2}}": "国际","{{__i == 4}}" : "独家" , "{{__i == 5}}": "军事",# "{{__i == 6}}" : "财经" , "{{__i == 7}}": "科技",# "{{__i == 8}}" : "体育" , "{{__i == 9}}": "娱乐",# "{{__i == 10}}": "时尚" , "{{__i == 11}}": "汽车",# "{{__i == 12}}": "房产" , "{{__i == 13}}": "航空",# "{{__i == 14}}": "健康"}#回调函数,解析方法def parse(self, response):#方式1:获取新闻标题的列表,scrapy框架里也支持xpath语法# news_title_list = response.xpath('//div[contains(@ne-if,"{{")]/div/a/text()').extract_first()# print("news_title_list::",news_title_list)# print("news_title_list长度:",len(news_title_list)) #长度即新闻的个数#方式2:获取标题和类别cate_mun__list = response.xpath('//*[contains(@ne-if,"{{")]/@ne-if').extract()print("cate_mun__list:",cate_mun__list)for cate_mun in cate_mun__list:#判断cate_mun是否在字典映射表中,只返回有的;循环15次,只拿符合条件的if cate_mun in self.cate_mun_map:#需要爬取的板块:cate_mun = self.cate_mun_map.get(cate_mun)print(cate_mun)

run以下bin文件:取到了6个分类

❷ 根据这6个分类,紧接着去循环爬取新闻标题和url:

这样就能将每个分类下的tab名称和新闻标题及url关联上

就相当于在上面代码中进行循环嵌套

import scrapyclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#建立一个tab分类名称关系映射字典:cate_mun_map = {"{{__i == 0}}" : "要闻" , "{{__i == 1}}": "上海","{{__i == 3}}" : "国内" , "{{__i == 2}}": "国际","{{__i == 4}}" : "独家" , "{{__i == 5}}": "军事",# "{{__i == 6}}" : "财经" , "{{__i == 7}}": "科技",# "{{__i == 8}}" : "体育" , "{{__i == 9}}": "娱乐",# "{{__i == 10}}": "时尚" , "{{__i == 11}}": "汽车",# "{{__i == 12}}": "房产" , "{{__i == 13}}": "航空",# "{{__i == 14}}": "健康"}#回调函数,解析方法def parse(self, response):#方式1:获取新闻标题的列表,scrapy框架里也支持xpath语法# news_title_list = response.xpath('//div[contains(@ne-if,"{{")]/div/a/text()').extract_first()# print("news_title_list::",news_title_list)# print("news_title_list长度:",len(news_title_list)) #长度即新闻的个数#方式2:获取标题和类别cate_mun__list = response.xpath('//*[contains(@ne-if,"{{")]/@ne-if').extract()print("cate_mun__list:",cate_mun__list)for cate_mun in cate_mun__list:#判断cate_mun是否在字典映射表中,只返回有的;循环15次,只拿符合条件的if cate_mun in self.cate_mun_map:#需要爬取的板块:cate_title = self.cate_mun_map.get(cate_mun)print(cate_title)#爬取每一个板块的新闻标题:# response.xpath('//*[contains(@ne-if,"{{__i == 0}},{{__i == 1}}")/div/a')#不能写死i==0,==1,所以用他对应的cate_mun来做循环:news_selector_list = response.xpath(f'//*[contains(@ne-if,"{cate_mun}")]/div/a')for news_selector in news_selector_list:news_title = news_selector.xpath("text()").extract_first()news_link = news_selector.xpath("@href").extract_first()print((news_title,news_link,cate_title))

再run一下bin文件:就得到了每个新闻分类下所有的新闻标题+每个新闻链接

⑶ 批量爬取新闻内容

根据上面取到的新闻分类、新闻标题、新闻链接

接下来还差最后一步:爬取到所有对应分类下的、所有新闻标题里的新闻内容

import scrapy
from scrapy.http import Requestclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#建立一个tab分类名称关系映射字典:cate_mun_map = {"{{__i == 0}}" : "要闻" , "{{__i == 1}}": "上海","{{__i == 3}}" : "国内" , "{{__i == 2}}": "国际","{{__i == 4}}" : "独家" , "{{__i == 5}}": "军事",# "{{__i == 6}}" : "财经" , "{{__i == 7}}": "科技",# "{{__i == 8}}" : "体育" , "{{__i == 9}}": "娱乐",# "{{__i == 10}}": "时尚" , "{{__i == 11}}": "汽车",# "{{__i == 12}}": "房产" , "{{__i == 13}}": "航空",# "{{__i == 14}}": "健康"}#回调函数,解析方法def parse(self, response):#获取标题和类别cate_mun__list = response.xpath('//*[contains(@ne-if,"{{")]/@ne-if').extract()print("cate_mun__list:",cate_mun__list)for cate_mun in cate_mun__list:#判断cate_mun是否在字典映射表中,只返回有的;循环15次,只拿符合条件的if cate_mun in self.cate_mun_map:#需要爬取的板块:cate_title = self.cate_mun_map.get(cate_mun)print(cate_title)#爬取每一个板块的新闻标题:# response.xpath('//*[contains(@ne-if,"{{__i == 0}},{{__i == 1}}")/div/a')#不能写死i==0,==1,所以用他对应的cate_mun来做循环:news_selector_list = response.xpath(f'//*[contains(@ne-if,"{cate_mun}")]/div/a')for news_selector in news_selector_list:news_title = news_selector.xpath("text()").extract_first()news_link = news_selector.xpath("@href").extract_first()print((news_title,news_link,cate_title))'''#根据源码照猫画虎:只要yield Request请求,就会自动帮我们将请求压缩到Scheduler、Dowloader参数为url,去重,回调解析函数这里的url是双层for循环下的6个新闻tab分类对应的700多个url回调函数需要自定义,用于解析内容;回调函数是下面的parse_news_detail方法注意:callback self的时候不要加括号'''yield Request(url=news_link,dont_filter=True,callback=self.parse_news_detail)#parse_news_detail方法,相当于是框架自动来完成700多次调用和响应def parse_news_detail(self, response):print("response::",response)#针对返回的response信息解析:#提取content的text内容content_list = response.xpath('//*[@id="content"]/div[@class="post_body"]/p/text()').extract()#拼接内容:content = "".join(content_list)print("content:",content)

run以下bin文件:但是content并没有被解析出来

原因是爬的过程,网易需要ua头,使用框架的好处就是,不再需要我们自己添加ua头

只需要在settings.py这个配置文件中,将17行放开,代码执行的过程中,框架就会自动拿到ua帮我放进去;

另外21行,是是否遵循机器人协议,默认为True,必要的时候可以改为False不遵循,但不建议改

然后重新run以下bin文件执行代码,还有两个问题

1、新闻分类的tab名称、和新闻标题及内容是分开的

2、新闻的content为空tab名称、和新闻标题及内容

先来解决第一个问题:tab名称、和新闻标题及内容把放在一起

在yield里面加上一个框架自带的meta字典,它作用是随着yield Request请求的发送,将各自的meta写进各自的response里面,这样就简单高效完成了放在一起的关联

import scrapy
from scrapy.http import Requestclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#建立一个tab分类名称关系映射字典:cate_mun_map = {"{{__i == 0}}" : "要闻" , "{{__i == 1}}": "上海","{{__i == 3}}" : "国内" , "{{__i == 2}}": "国际","{{__i == 4}}" : "独家" , "{{__i == 5}}": "军事",# "{{__i == 6}}" : "财经" , "{{__i == 7}}": "科技",# "{{__i == 8}}" : "体育" , "{{__i == 9}}": "娱乐",# "{{__i == 10}}": "时尚" , "{{__i == 11}}": "汽车",# "{{__i == 12}}": "房产" , "{{__i == 13}}": "航空",# "{{__i == 14}}": "健康"}#回调函数,解析方法def parse(self, response):#获取标题和类别cate_mun__list = response.xpath('//*[contains(@ne-if,"{{")]/@ne-if').extract()for cate_mun in cate_mun__list:#判断cate_mun是否在字典映射表中,只返回有的;循环15次,只拿符合条件的if cate_mun in self.cate_mun_map:#需要爬取的板块:cate_title = self.cate_mun_map.get(cate_mun)#爬取每一个板块的新闻标题:# response.xpath('//*[contains(@ne-if,"{{__i == 0}},{{__i == 1}}")/div/a')#不能写死i==0,==1,所以用他对应的cate_mun来做循环:news_selector_list = response.xpath(f'//*[contains(@ne-if,"{cate_mun}")]/div/a')for news_selector in news_selector_list:news_title = news_selector.xpath("text()").extract_first()news_link = news_selector.xpath("@href").extract_first()'''#根据源码照猫画虎:只要yield Request请求,就会自动帮我们将请求压缩到Scheduler、Dowloader参数为url,去重,回调解析函数这里的url是双层for循环下的6个新闻tab分类对应的700多个url回调函数需要自定义,用于解析内容;回调函数是下面的parse_news_detail方法注意:callback self的时候不要加括号'''yield Request(url=news_link,dont_filter=True,callback=self.parse_news_detail,meta={"news_title":news_title,"cate_title":cate_title})#parse_news_detail方法,相当于是框架自动来完成700多次调用和响应def parse_news_detail(self, response):news_title = response.meta.get("news_title")cate_title = response.meta.get("cate_title")#针对返回的response信息解析:#提取content的text内容content_list = response.xpath('//*[@id="content"]/div[@class="post_body"]/p/text()').extract()#拼接内容:content = "".join(content_list)print(cate_title,news_title,content)

Item及 PipeLine

Item(项目)

抓取的主要目标是从非结构化源(通常是网页)中提取结构化数据。

Scrapy蜘蛛可以像Python一样返回提取的数据。

虽然方便和熟悉,但很容易在字段名称中输入拼写错误或返回不一致的数据,尤其是在具有许多蜘蛛的较大项目中。

为了定义通用输出数据格式,Scrapy提供了Item类。

Item对象是用于收集抓取数据的简单容器。

它们提供类似字典的 API,并具有用于声明其可用字段的方便语法。

====================================================================

简单来说:

上面的实战案例中,我们初步完成了数据的爬取

接下来就是要进行数据清洗、和持久化存储到数据库

在'Newspro'的项目文件夹下,已经自动生成了一个'Item.py'的文件:

在Scrapy中,数据模型使用Item类来定义。在这个文件中,定义了一个名为NewsproItem的Item类,它继承自scrapy.Item类。

# define the fields for your item here like:
# name = scrapy.Field()

上面这两句话的意思是:

在这个Item类中,可以定义需要抓取的数据的字段。

每个字段都可以使用scrapy.Field()来定义,以便在抓取过程中存储相应的数据

上面"(3) 批量爬取新闻内容"最后的代码中"print(cate_title,news_title,content)",我们已经构建出来三个数据,由于没有一个统一的类来管理它们,所以输出打印结果看起来就很乱

所以我们在'Item.py'文件中使用scrapy.item来定义字段、管理它们:

import scrapyclass NewsItem(scrapy.Item):title = scrapy.Field()cata = scrapy.Field()content = scrapy.Field()

然后去"wangyi.py"文件中:进行封装item

import scrapy
from scrapy.http import Request
from NewsPro.items import NewsItem  #记得导入tiem;NewsPro是项目根目录class WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#建立一个tab分类名称关系映射字典:cate_mun_map = {"{{__i == 0}}" : "要闻" , "{{__i == 1}}": "上海","{{__i == 3}}" : "国内" , "{{__i == 2}}": "国际","{{__i == 4}}" : "独家" , "{{__i == 5}}": "军事",# "{{__i == 6}}" : "财经" , "{{__i == 7}}": "科技",# "{{__i == 8}}" : "体育" , "{{__i == 9}}": "娱乐",# "{{__i == 10}}": "时尚" , "{{__i == 11}}": "汽车",# "{{__i == 12}}": "房产" , "{{__i == 13}}": "航空",# "{{__i == 14}}": "健康"}#回调函数,解析方法def parse(self, response):#获取标题和类别cate_mun__list = response.xpath('//*[contains(@ne-if,"{{")]/@ne-if').extract()for cate_mun in cate_mun__list:#判断cate_mun是否在字典映射表中,只返回有的;循环15次,只拿符合条件的if cate_mun in self.cate_mun_map:#需要爬取的板块:cate_title = self.cate_mun_map.get(cate_mun)#爬取每一个板块的新闻标题:# response.xpath('//*[contains(@ne-if,"{{__i == 0}},{{__i == 1}}")/div/a')#不能写死i==0,==1,所以用他对应的cate_mun来做循环:news_selector_list = response.xpath(f'//*[contains(@ne-if,"{cate_mun}")]/div/a')for news_selector in news_selector_list:news_title = news_selector.xpath("text()").extract_first()news_link = news_selector.xpath("@href").extract_first()'''#根据源码照猫画虎:只要yield Request请求,就会自动帮我们将请求压缩到Scheduler、Dowloader参数为url,去重,回调解析函数这里的url是双层for循环下的6个新闻tab分类对应的700多个url回调函数需要自定义,用于解析内容;回调函数是下面的parse_news_detail方法注意:callback self的时候不要加括号'''yield Request(url=news_link,dont_filter=True,callback=self.parse_news_detail,meta={"news_title":news_title,"cate_title":cate_title})#parse_news_detail方法,相当于是框架自动来完成700多次调用和响应def parse_news_detail(self, response):news_title = response.meta.get("news_title")cate_title = response.meta.get("cate_title")#针对返回的response信息解析:#提取content的text内容content_list = response.xpath('//*[@id="content"]/div[@class="post_body"]/p/text()').extract()#拼接内容:content = "".join(content_list)print(cate_title,news_title,content)'''实例化封装item对象:目的1、统一数据目的2、方便item调度      '''newItem = NewsItem()NewsItem["title"] = news_titleNewsItem["cate"] = cate_titleNewsItem["content"] = contentyield newItem

结合最上面的scrapy架构图来说说,为什么要封装item?

只要yield的是item对象,引擎就会把item交给pipelines,做数据清洗和存储(对应架构图的7到8);

如果yield的是Request对象,相当于是压到了Scheduler(第三步),重新请求Engine(第六步),重新响应解析;

----------------->>>>>

代码到这里,由于前面数据都已经准备好了,所以不需要在重新发起请求,直接return item,即构建了700多个新闻对应的item对象;

=====================================================================

所以总的来说:解析就两步

没解析完成,就yield Request

解析完成,拿到数据,就yield item对象

PipeLine

在一个项目被scpay抓取之后,它被发送到项目管道,该项目管道通过顺序执行的几个组件处理它。

每个项目管道组件(有时简称为“项目管道”)是一个实现简单方法的Python类。他们收到一个项目并对其执行操作,同时决定该项目是否应该继续通过管道或被丢弃并且不再处理。

项目管道的典型用途是:

> - cleansing HTML data:清理HTML数据
> - validating scraped data (checking that the items contain certain fields):验证爬取的数据
> - checking for duplicates (and dropping them):检查重复数据
> - storing the scraped item in a database:存储爬取到的数据

=====================================================================

每个项管道组件都是一个必须实现以下方法的Python类:

process_item(self,项目,蜘蛛)
为每个项目管道组件调用此方法:process_item() 

返回带数据的dict、一个Item (或任何后代类)对象,返回Twisted Deferred或引发 DropItem异常。丢弃的项目不再由其他管道组件处理。

-------------------------------------------------------------------------------------------------------------------------

此外,他们还可以实现以下方法:

open_spider(self,蜘蛛):打开蜘蛛时会调用此方法

close_spider(self,蜘蛛):当蜘蛛关闭时调用此方法。

from_crawler(cls,crawler ):如果存在,则调用此类方法以从a创建管道实例Crawler。它必须返回管道的新实例。Crawler对象提供对所有Scrapy核心组件的访问,如设置和信号; 它是管道访问它们并将其功能挂钩到Scrapy的一种方式。

上面提到,只要yield的是item对象,就会由item pipelines来接收

接下来,我们进入到项目创建时自动生成的"pipelines.py"文件中:

 直接添加打印item:

为了确保"pipelines.py"文件中的"process_item"方法会工作,需要去"settings.py"文件:

将66~68行的代码放开(默认是注释掉的),让它开启工作

Pipeline的作用:
"Newspro.pipelines.NewsproPipeline"可以构建多个,每个Pipeline都可以有不同的功能

比如:

写3个Pipeline,给每个Pipeline加上权重,执行的时候会依次按照从上到下的顺序:

第一个Pipeline是把数据存在文件里,执行完交给第二个Pipeline

第二个Pipeline是把数据存在mysql里,执行完交给第三个Pipeline

第三个Pipeline是把数据存在mongo里

多个Pipeline就是这样以此类推,到这里就是scrapy的最后一步闭环动作

然后在"wangyi.py"文件中,提取content的text内容时加上去空格(第60行):

import scrapy
from scrapy.http import Request
from Newspro.items import NewsItemclass WangyiSpider(scrapy.Spider):name = "wangyi"# allowed_domains = ["news.163.com"]   #限制爬的域一定要在这个url之下,所以先注释start_urls = ["http://news.163.com/"]  #爬虫起始地址#建立一个tab分类名称关系映射字典:cate_mun_map = {"{{__i == 0}}" : "要闻" , "{{__i == 1}}": "上海","{{__i == 3}}" : "国内" , "{{__i == 2}}": "国际","{{__i == 4}}" : "独家" , "{{__i == 5}}": "军事",# "{{__i == 6}}" : "财经" , "{{__i == 7}}": "科技",# "{{__i == 8}}" : "体育" , "{{__i == 9}}": "娱乐",# "{{__i == 10}}": "时尚" , "{{__i == 11}}": "汽车",# "{{__i == 12}}": "房产" , "{{__i == 13}}": "航空",# "{{__i == 14}}": "健康"}#回调函数,解析方法def parse(self, response):#获取标题和类别cate_mun__list = response.xpath('//*[contains(@ne-if,"{{")]/@ne-if').extract()for cate_mun in cate_mun__list:#判断cate_mun是否在字典映射表中,只返回有的;循环15次,只拿符合条件的if cate_mun in self.cate_mun_map:#需要爬取的板块:cate_title = self.cate_mun_map.get(cate_mun)#爬取每一个板块的新闻标题:# response.xpath('//*[contains(@ne-if,"{{__i == 0}},{{__i == 1}}")/div/a')#不能写死i==0,==1,所以用他对应的cate_mun来做循环:news_selector_list = response.xpath(f'//*[contains(@ne-if,"{cate_mun}")]/div/a')for news_selector in news_selector_list:news_title = news_selector.xpath("text()").extract_first()news_link = news_selector.xpath("@href").extract_first()'''#根据源码照猫画虎:只要yield Request请求,就会自动帮我们将请求压缩到Scheduler、Dowloader参数为url,去重,回调解析函数这里的url是双层for循环下的6个新闻tab分类对应的700多个url回调函数需要自定义,用于解析内容;回调函数是下面的parse_news_detail方法注意:callback self的时候不要加括号'''yield Request(url=news_link,dont_filter=True,callback=self.parse_news_detail,meta={"news_title":news_title,"cate_title":cate_title})#parse_news_detail方法,相当于是框架自动来完成700多次调用和响应def parse_news_detail(self, response):news_title = response.meta.get("news_title")cate_title = response.meta.get("cate_title")#针对返回的response信息解析:#提取content的text内容content_list = response.xpath('//*[@id="content"]/div[@class="post_body"]/p/text()').extract()#拼接内容、并去除空格content = "".join([i.strip() for i in content_list])print(cate_title,news_title,content)'''实例化封装item对象:目的1、统一数据目的2、方便item调度      '''newItem = NewsItem()newItem["title"] = news_titlenewItem["cate"] = cate_titlenewItem["content"] = contentyield newItem

重新run一下bin文件:

cate(tab分类标题)、title(新闻标题)、content(新闻内容)已经放在一起了

但是仍有一些content内容没有解析到的,这是前面遗留的第二个问题("⑶ 批量爬取新闻内容"这里),是由于"news_selector_list"这里xapth定义的解析规则不能适用所有的新闻分类导致的

所以这里我再做下处理,先把content为空的过滤掉(在"process_item"文件里处理)

可以导入from scrapy.extensions import DropItem这个类,用来丢弃item

from scrapy.extensions import DropItem #丢弃itemclass NewsproPipeline:def process_item(self, item, spider):#加个判断条件,content为空的不返回if not item["content"]:DropItem("content不能为空,丢弃!")else:print("item:::", item)return item #一定要记得加return进行传递

再重新run一下bin文件:在控制台搜索一下为空的content,显示0个,说明已经过滤成功了

到这里,就已经使用scrapy框架完成了整个爬虫过程:请求url -> 解析数据  -> 清洗数据

下一章接着一起来学习scrapy框架的进一步学习哦~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/195054.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决websocket集群的session共享问题

在websocket中&#xff0c;服务端主要使用的是session打交道&#xff0c;但是由于session无法实现序列化&#xff0c;不能存储到redis这些中间存储里面&#xff0c;因此这里我们只能把session存储在本地的内存中&#xff0c;那么如果是集群的话&#xff0c;我们如何实现session…

前端笔记(三)CSS 盒子模型

结构伪类选择器 基本的结构伪类选择器 可以根据元素的结构关系来查找元素 比如列标签 li&#xff0c;使用 li:first-child { background-color: green; }就可以选中第一个该标签。 <!DOCTYPE html> <html lang"en"> <head><meta charset&q…

智慧能源:数字孪生压缩空气储能管控平台

压缩空气储能在解决可再生能源不稳定性和提供可靠能源供应方面具有重要的优势。压缩空气储能&#xff0c;是指在电网负荷低谷期将电能用于压缩空气&#xff0c;在电网负荷高峰期释放压缩空气推动汽轮机发电的储能方式。通过提高能量转换效率、增加储能密度、快速启动和调节能力…

如何知道B站各分区直播数据趋势?

随着短视频时代的来临&#xff0c;直播行业也越来越火爆&#xff0c;很多博主开启直播之路&#xff0c;B站也顺应了时代发展所需&#xff0c;在直播板块投入颇多&#xff0c;那么在B站开直播&#xff0c;我们应该如何知晓B站每个分区的直播数据情况呢&#xff1f; 借用第三方数…

MySQL练习题,学生成绩查询练习题,附带答案

题目 (一) 新建以下几个表 student(学生表)&#xff1a; snosnamesexdeptbirthagePhone 其中约束如下&#xff1a; &#xff08;1&#xff09; 学号不能存在相同的 sno int auto_increment primary key &#xff08;2&#xff09; 名字为非空 sname varchar(20) not nu…

Excel如何设置在未打印时显示虚线打印时不显示虚线

记得之前分享过一个BOM表模板&#xff0c;但是在我打印时&#xff0c;发现明明是留空白的地方却打印出来的虚线 后来&#xff0c;看了自己的页面布局&#xff0c;原来是网格线设置错误了 当我设置为查看时显示网格线&#xff0c;打印时不显示网格线&#xff0c;这样就正常了

苹果配件妙控鼠标、键盘、触控板值得入手吗

大家好&#xff0c;我是极智视界&#xff0c;欢迎关注我的公众号&#xff0c;获取我的更多前沿科技分享 邀您加入我的知识星球「极智视界」&#xff0c;星球内有超多好玩的项目实战源码和资源下载&#xff0c;链接&#xff1a;https://t.zsxq.com/0aiNxERDq 苹果的优质和成功绝…

STM32存储左右互搏 SPI总线读写FRAM MB85RS16

STM32存储左右互搏 I2C总线读写FRAM MB85RS16 在中低容量存储领域&#xff0c;除了FLASH的使用&#xff0c;&#xff0c;还有铁电存储器FRAM的使用&#xff0c;相对于FLASH&#xff0c;FRAM写操作时不需要预擦除&#xff0c;所以执行写操作时可以达到更高的速度&#xff0c;其…

40 mysql join 的实现

前言 join 是一个我们经常会使用到的一个 用法 我们这里 看一看各个场景下面的 join 的相关处理 测试数据表如下, 两张测试表, tz_test, tz_test03, 表结构 一致 CREATE TABLE tz_test (id int(11) unsigned NOT NULL AUTO_INCREMENT,field1 varchar(128) DEFAULT NULL,fi…

BGP多跳及BGP4+

一、知识补充 1、BGP4 传统BGP-4只管理IPV4路由信息&#xff0c;对于使用其它网络程协议 (若IPV6等)的应用末给予支持。IETF对BGP-4扩展&#xff0c;提出BGP4&#xff0c;可以提供对IPV6、IPX和MPLS VPN的支持 (简单说: 扩展IPV6协议栈支持)。 2、全互联 在上一篇博文中提…

leetcode - 矩阵区域和

1314. 矩阵区域和 - 力扣&#xff08;LeetCode&#xff09; 给你一个 m x n 的矩阵 mat 和一个整数 k &#xff0c;请你返回一个矩阵 answer &#xff0c;其中每个 answer[i][j] 是所有满足下述条件的元素 mat[r][c] 的和&#xff1a; i - k < r < i k, j - k < c …

备忘录模式 rust和java的实现

文章目录 备忘录模式介绍实现javarustrust仓库 备忘录模式 备忘录&#xff08;Memento&#xff09;模式的定义&#xff1a;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态&#xff0c;以便以后当需要时能将该对象恢复到原先…

【5G PHY】5G NR 如何计算资源块的数量?

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

严蔚敏数据结构p17(2.19)——p18(2.24) (c语言代码实现)

目录 2.19已知线性表中的元素以值递增有序排列,并以单链表作存储结构。试写一高效的算法,删除表中所有值大于 mink 且小于 maxk 的元素(若表中存在这样的元素&#xff09;同时释放被删结点空间,并分析你的算法的时间复杂度(注意:mink 和 maxk 是给定的个参变量,它们的值可以和表…

Hdoop学习笔记(HDP)-Part.12 安装HDFS

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

继阿里云、滴滴、语雀后,腾讯视频也出现重大系统故障

昨晚&#xff0c;许多网友报告称腾讯视频出现了网络故障&#xff0c;具体表现为首页无法加载内容、VIP 用户无法观看会员视频等问题。 针对这一问题&#xff0c;腾讯视频回应称&#xff1a;目前腾讯视频遇到了暂时的技术问题&#xff0c;正在紧急修复中&#xff0c;各项功能正在…

在项目根目录未找到 app.json

这个问题就是我们在编译后的app.json文件找不到&#xff0c;路径出现了问题 首先看dist下我们该文件的路径 所以我们需要将该路径配置到我们project.config.json文件中去 在这里新加下面这行代码就可以了&#xff0c; "miniprogramRoot": "dist/dev/mp-weixi…

C语言扫雷游戏

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、扫雷游戏的分析和设计1.1扫雷游戏的功能说明1.2数据结构的分析1.3文件结构设计 二、扫雷游戏的代码实现总结 前言 详细介绍扫雷游戏的思路和实现过程。 一…

基于springboot + vue体育馆使用预约平台

qq&#xff08;2829419543&#xff09;获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;springboot 前端&#xff1a;采用vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xf…

自动提交日志脚本(6)浏览器抓包日志提交的数据

主要完成 do_logigdo_write_log 通过python的requests库post数据上传&#xff0c;因为是公司的系统我就展示抓包了&#xff0c;不展示怎么写了。 这边用日志暂存的页面做展示。 步骤 打开对应的页面&#xff0c;再打开浏览器的开发人员工具【一般是按f12】点击暂存按钮&…