专业爬虫框架 -- 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.mfbz.cn/a/214503.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HCIP —— 双点重发布 + 路由策略 实验

目录 实验拓扑&#xff1a; 实验要求&#xff1a; 实验配置&#xff1a; 1.配置IP地址 2.配置动态路由协议 —— RIP 、 OSPF R1 RIP R4 OSPF R2 配置RIP、OSPF 双向重发布 R3配置RIP、OSPF 双向重发布 3.查询路由表学习情况 4.使用路由策略控制选路 R2 R3 5.检…

【Google2023】利用TiDE进行长期预测实战(时间序列密集编码器)

一、本文介绍 大家好&#xff0c;最近在搞论文所以在研究各种论文的思想&#xff0c;这篇文章给大家带来的是TiDE模型由Goggle在2023.8年发布&#xff0c;其主要的核心思想是&#xff1a;基于多层感知机&#xff08;MLP&#xff09;构建的编码器-解码器架构&#xff0c;核心创…

GEE:梯度卷积

作者:CSDN @ _养乐多_ 本文将介绍在 Google Earth Engine(GEE)平台上,进行梯度卷积操作的代码框架、核心函数和多种卷积核,比如 Roberts、Prewitt、Sobel、各向同性算子、Compass算子、拉普拉斯算子、不同方向线性检测算子等。 结果如下图所示, 文章目录 一、常用的梯度…

实现一个简单的网络通信下(udp)

时间过去好久了&#xff0c;先回忆一下上一篇博客的代码&#xff01;&#xff01; 目前来看&#xff0c;我们客户端发一条消息&#xff0c;我服务器收到这一条消息之后呢&#xff0c;服务器也知道了是谁给我发来的消息&#xff0c;紧接这就把这条消息放进buffer当中&#xff0c…

POJ 3734 Blocks 动态规划(矩阵的幂)

一、题目大意 我们要给排成一行的区块涂颜色&#xff0c;可以选择红、绿、蓝、黄四种&#xff0c;要求红和绿的块都必须是偶数个&#xff0c;求出最终的涂色方式&#xff0c;对10007取余。 二、解题思路 我们设三个数列A&#xff0c;B和C&#xff1a; 1、A代表红色和绿色都…

百度收录批量查询工具,免费SEO优化排名工具

拥有一个在搜索引擎中得到良好收录的网站对于个人和企业都至关重要。而百度&#xff0c;作为中国最大的搜索引擎&#xff0c;其收录情况直接影响着网站的曝光度和流量。 百度搜索引擎是中文用户获取信息的重要途径之一。而在这个竞争激烈的网络环境中&#xff0c;了解自己网站…

QT 中 QTimer 类 备查

基础 // 指定了父对象, 创建的堆内存可以自动析构 QTimer::QTimer(QObject *parent nullptr);// 根据指定的时间间隔启动或者重启定时器, 需要调用 setInterval() 设置时间间隔 void QTimer::start();// 启动或重新启动定时器&#xff0c;超时间隔为msec毫秒。 void QTimer::…

游泳馆会员服务预约管理系统预约小程序效果如何

游泳馆在各地每天都有大量用户前往&#xff0c;夏季室外、冬季室内也是学习游泳技术和休闲娱乐的好地方&#xff0c;而消费者大多是年轻人和家长带的孩子&#xff0c;这部分群体更显年轻化&#xff0c;因此在如今互联网环境下&#xff0c;传统商家需要进一步赋能客户消费路径。…

共识问题:区块链如何确认记账权?

区块链可以说是最近几年最热的技术领域之一&#xff0c;区块链起源于中本聪的比特币&#xff0c;作为比特币的底层技术&#xff0c;本质上是一个去中心化的数据库&#xff0c;其特点是去中心化、公开透明&#xff0c;作为分布式账本技术&#xff0c;每个节点都可以参与数据库的…

力扣 --- 最长公共前缀

题目描述&#xff1a; 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 ""。 示例 1&#xff1a; 输入&#xff1a;strs ["flower","flow","flight"] 输出&#xff1a;"fl"…

蓝桥杯物联网竞赛_STM32L071_8_ADC扩展模块

原理图&#xff1a; 扩展模块原理图&#xff1a; RP1和RP2分别对应着AIN1和AIN2&#xff0c;扭动它们&#xff0c;其对应滑动变阻器阻值也会变化 实验板接口原理图&#xff1a; 对应实验板接口PB1和PB0 即AN1对应PB1, AN2对应PB0 CubMx配置&#xff1a; ADC通道IN8和IN9才对…

Spring MVC学习随笔-控制器(Controller)开发详解:控制器跳转与作用域(二)视图模板、静态资源访问

学习视频&#xff1a;孙哥说SpringMVC&#xff1a;结合Thymeleaf&#xff0c;重塑你的MVC世界&#xff01;&#xff5c;前所未有的Web开发探索之旅 衔接上文Spring MVC学习随笔-控制器(Controller)开发详解&#xff1a;控制器跳转与作用域&#xff08;一&#xff09; SpingMVC中…

智慧灯杆系统平台架构设计需要考虑的几个要点

智慧灯杆是一种集成了各种先进技术的道路照明设施。它不仅提供照明服务&#xff0c;还可以具有物联网技术、视频监控、环境监测、广播通讯、无线网络覆盖等多种功能。这些智能功能可以通过互联网进行控制和管理&#xff0c;从而实现智慧城市的建设。智慧灯杆能够提升城市的智能…

费解的开关

费解的开关 模拟一下开关的过程&#xff1a; 直接对第一行进行开关灯即可&#xff0c;那么第一行开关等的方案有多少个呢&#xff1f; 可以第一个想到的是5次&#xff0c;但实际上是25次&#xff0c;因为没有规定说只能开关一次吧。 那么如何获得这32种方案呢&#xff1f; 可…

【算法】前后缀分解题单⭐

文章目录 题单来源题目列表42. 接雨水238. 除自身以外数组的乘积2256. 最小平均差2483. 商店的最少代价代码1——前后缀数组代码2—— O ( 1 ) O(1) O(1)空间&#x1f402; 2420. 找到所有好下标2167. 移除所有载有违禁货物车厢所需的最少时间代码1——前后缀分解代码2——简洁…

【一个超简单的爬虫demo】探索新浪网:使用 Python 爬虫获取动态网页数据

探索新浪网&#xff1a;使用 Python 爬虫获取动态网页数据 引言准备工作选择目标新浪网的结构 编写爬虫代码爬取example.com爬取新浪首页部分内容解析代码注意&#xff1a; KeyError: href结果与展示 其他修改和适应注意事项 总结 引言 可以实战教爬虫吗&#xff0c;搭个环境尝…

利用STM32内置温度传感器实现温度监测系统

STM32微控制器是一款强大的嵌入式处理器&#xff0c;具有广泛的应用领域。其中&#xff0c;一些STM32微控制器内置了温度传感器&#xff0c;可以方便地实现温度监测功能。本文将详细介绍如何利用STM32内置温度传感器实现温度监测系统&#xff0c;并提供相应的示例代码。 一、硬…

python爬虫AES魔改案例:某音乐素材下载网

声明&#xff1a; 该文章为学习使用&#xff0c;严禁用于商业用途和非法用途&#xff0c;违者后果自负&#xff0c;由此产生的一切后果均与作者无关 一、找出需要加密的参数 js运行 atob(‘aHR0cHM6Ly93d3cuYWlnZWkuY29tL3NvdW5kL2NsYXNzLw’) 拿到网址&#xff0c;F12打开调…

Nginx配置反向代理与负载均衡

Nginx配置反向代理与负载均衡 一、代理服务1.正向代理2.反向代理 二、实战场景-反向代理1.修改nginx配置 -> nginx.conf文件2.修改前端路径 三、实战场景-负载均衡1.热备2.轮询3.加权轮询4.ip_hash ​ Nginx (“engine x”) 是一个高性能的HTTP和反向代理服务器&#xff0c;…

ubuntu下快速搭建docker环境训练yolov5数据集

参考文档 yolov5-github yolov5-github-训练文档 csdn训练博客 一、配置环境 1.1 安装依赖包 前往清华源官方地址 选择适合自己的版本替换自己的源 # 备份源文件 sudo cp /etc/apt/sources.list /etc/apt/sources.list_bak # 修改源文件 # 更新 sudo apt update &&a…
最新文章