当前位置: 首页 > article >正文

专业爬虫框架 -- 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 -h
 scrapy <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 scrapy

class 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 scrapy

class 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 scrapy

class 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 scrapy

class 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 scrapy

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):

        #方式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 scrapy

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):

        #方式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 Request


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()
        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 Request

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及 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 scrapy

class 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_title
        NewsItem["cate"] = cate_title
        NewsItem["content"] = content

        yield 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 NewsItem

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([i.strip() for i in content_list])
        print(cate_title,news_title,content)

        '''
        实例化封装item对象:
        目的1、统一数据
        目的2、方便item调度      
        '''
        newItem = NewsItem()
        newItem["title"] = news_title
        newItem["cate"] = cate_title
        newItem["content"] = content

        yield 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 #丢弃item

class 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.kler.cn/a/156741.html

相关文章:

  • git配置远程仓库的认证信息
  • 将Excel文件的两个表格经过验证后分别读取到Excel表和数据库
  • LeetCode【0035】搜索插入位置
  • ubuntu-desktop-24.04上手指南(更新阿里源、安装ssh、安装chrome、设置固定IP、安装搜狗输入法)
  • OCR识别铁路电子客票
  • WebSocket和HTTP协议的性能比较与选择
  • C++继承(详解)
  • 聚焦数据库Serverless创新,就在2023亚马逊云科技re:Invent
  • 科技云报道:AI+PaaS,中国云计算市场迎来新“变量”?
  • React Native expo Android adb 调试出现 device not found 怎么办
  • springMVC实验(二)—调式工具APIFOX的使用
  • 物品领用管理软件哪家的好用?怎么让办公用品管理变得更加轻松高效?
  • 智能优化算法应用:基于海洋捕食者算法无线传感器网络(WSN)覆盖优化 - 附代码
  • Python小项目:葛兰中欧医疗基金数据分析
  • 无公网IP环境固定地址远程SSH访问本地树莓派Raspberry Pi
  • 23种设计模式之C++实践(一)
  • Beta冲刺随笔-DAY6-橘色肥猫
  • Android防破解重签名方案研究
  • Lab 3: Recursion, Tree Recursion(CS61A 2020)
  • JAVA代码优化:随机数字生成(UUID)
  • Unity EventSystem的一些理解和使用
  • 论文阅读:Distributed Initialization for VVIRO with Position-Unknown UWB Network
  • 网页封装APP的原理:将网页转化为移动应用
  • Flask 自定义template和静态文件路径
  • 大华技术GIS开发工程师24届秋招三场面试Offer面经
  • 2023年中国中高端服饰品牌经营现状和市场发展趋势分析|徐礼昭