Scrapy爬虫框架的原理与实战[阳光政务爬虫项目]

引言:Scrapy是Python开发的一个快速、高层次的屏幕抓取和web抓取框架用于抓取web站点并从页面中提取结构化的数据。Scrapy用途广泛,可以用于数据挖掘、监测和自动化测试。

一、Scrapy架构的来源与详解

首先,框架的作用本质是将一堆轮子融合在一起,可以方便快速的解决一类问题。
最初,我们在入门学习爬虫使用requests库时,一套基本的爬取网页内容流程如下:
在这里插入图片描述
基本流程伪代码如下:

import requests
from lxml import etree

// 1.URL队列
urls = [
    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16488',
    'https://aaai.org/ocs/index.php/AAAI/AAAI18/paper/viewPaper/16583',
    ...
]
data = []
for url in urls:
	// 2.发送请求
    response = requests.get(url)  
    html = response.content       // 获取网页内容(content返回的是bytes型数据,text()获取的是Unicode型数据)
    // 3.内容提取
    title = etree.HTML(html).xpath('//*[@id="title"]/text()') 
	// 4.数据队列
	data.append(title)
// 保存数据.....

而以上流程基本在爬取过程中是固定不变的,因此诞生了Scrapy爬虫框架,用来整合上面的每一步骤,用户无需编写每一步的代码,仅需要改写需要自定义的模块即可。下图为Scrapy架构图:
在这里插入图片描述
【注意】:于上图基本流程不同的是,从爬虫➡️调度器,以及调度器➡️下载器不再以URL传递,而是以封装好的Requests请求传递
下面为Scrapy各个组件的功能介绍:

组件功能是否实现
Scrapy Engine(引擎)总指挥:负责不同模块间数据的传递已实现
Scheduler(调度器)一个存放引擎发过来的request请求队列已实现
Downloader(下载器)下载从引擎发过来的requests请求,并返回给引擎已实现
Downloader Middlewares
(下载中间件)
可自定义的下载扩展,比如设置代理可选改写
Spider(爬虫)处理引擎发来的response
1⃣️提取数据,交给引擎传输到管道进行处理和存储
2⃣️提取URL
组装requests请求并从引擎传输到调度器
需手写
Spider Middlewares
(爬虫中间件)
可自定义requests请求进行response过滤可选改写
Item Pipeline(管道)处理引擎传过来的数据,比如存储需手写

【注意】:使用Scrapy爬虫框架,可提升可靠性,低耦合,因为以上模块相关且独立,若其中一个模块出错,则不影响其他模块的运行。

二、Scrapy模块的安装与初始配置

2.1 安装Scrapy
$ pip install scrapy
2.2 创建Scrapy项目
$ scrapy startproject <projectName>

创建成功后根据终端提示进入项目目录:

$ cd <projectName>
2.3 生成Scrapy爬虫
$ scrapy genspider <spiderName> <domin>

以上命令需要两个参数,分别是自定义的爬虫名(即新建.py爬虫文件的名字),以及要爬取网址的域名
例如要爬取百度中的某一页面就可以写成如下格式:

$ scrapy genspider baidu baidu.com
2.4. 执行Scrapy爬虫
$ scrapy crawl <spiderName>
2.5. 使用Scrapy Shell

在未启动Scrapy爬虫的情况下,可以使用shell进行debug和测试:

$ scrapy shell <爬取网站网址>
2.6 修改Scrapy项目settings.py文件

在编写爬虫代码前,我们需要先修改配置文件:
在这里插入图片描述
如上图,我们需要设置USER_AGENT为自己浏览器的代理,表示我们模拟浏览器登录。然后我们进行如下设置:

ROBOTSTXT_OBEY = False    # 不遵循爬虫协议
LOG_LEVEL = 'WARN'        # 设置日志级别为'WARN',则仅打印警告以上级别的日志

日志的四个级别优先级由高到低分别是:

  • ERROR
  • WARN
  • INFO
  • DEBUG

同时,还有解除ITEM_PIPELINES注释,如下图:
在这里插入图片描述
该配置项表示可以使用管道,后面的值表示距离,即优先级值越小,管道距离越近,优先执行距离近的管道。如下,优先执行MyspiderPipeline1,然后在执行MyspiderPipeline

ITEM_PIPELINES = {
   'mySpider.pipelines.MyspiderPipeline': 300,
   'mySpider.pipelines.MyspiderPipeline1': 299,
}

以下通过文件树列出创建后的scrapy项目文件,并注释了主要文件的功能:

$ tree
.
├── mySpider
│   ├── __init__.py
│   ├── __pycache__
│   ├── items.py         # 自定义需要爬取的内容
│   ├── middlewares.py   # 下载中间件+爬虫中间件,可自定义
│   ├── pipelines.py     # 管道,用于数据保存
│   ├── settings.py      # 设置文件,如代理、日志级别等
│   └── spiders
│       ├── __init__.py
│       ├── __pycache__
│       └── itcast.py    # 生成的爬虫文件
└── scrapy.cfg

三、基本代码入门

在Scrapy框架中,我们至少需要修改爬虫管道文件以实现最基本的爬虫。

3.1 编辑爬虫文件

在上一步生成Scrapy爬虫后可在新建项目的spiders文件夹下看到爬虫文件,如下图。此时,需要将start_urls修改为自己最开始要爬取的页面URL,并在parse函数中编写页面解析代码,此处使用response自带的XPath方法选取节点。[不懂XPath的用法见此]
在这里插入图片描述
由于xpath返回的文本内容Selector特殊列表,因此我们还需要使用extract()extract_first()函数提取数据文本内容,具体代码如下:

class ItcastSpider(scrapy.Spider):
    name = 'itcast'                                               # 爬虫名
    allowed_domains = ['itcast.cn']                               # 允许爬取的范围
    start_urls = ['http://www.itcast.cn/channel/teacher.shtml']   # 最开始请求的URL地址

    def parse(self, response):    # 数据提取方法,接收来自下载中间件传来的response
        // 分组
        li_list = response.xpath('//div[@class="tea_con"]//li') # xpath返回含有selector对象的列表
        for li in li_list:
            item = {}
            item['name'] = li.xpath('.//h3/text()').extract()[0]
            item['title'] = li.xpath('.//h40/text()').extract_first()
            yield item

此处,笔者强烈建议使用extract_first()函数,其等价于extract()[0],主要优点是如果提取出空字符串,则返回None而不是空列表,这里使得我们无需再判断是否为空!

最后使用关键字yield进行中断返回值,相比return优点在于,借用生成器特点逐个返回值从而减少内存占用

此处返回值类型必须如下,如果是列表则会报错

返回值类型Request(向调度器传)BaseItemdict(向管道传)None
3.2 编辑管道文件

管道文件是pipelines.py,此处使用MongoDB保存爬取的数据[Python安装并操作MongoDB见此]

// pipelines.py
from pymongo import MongoClient


client = MongoClient('127.0.0.1', 27017)
db = client.LaGou
col = db.info

class LagouPipeline(object):
    def process_item(self, item, spider):
        col.insert_many([item])
        return item

以上便可实现最基本的爬取数据入库的流程,方便理解,以下将进行完整爬虫项目。

四、实战Scrapy之阳光政务平台爬虫项目

【项目描述】:爬取「东莞阳光热线问政平台」中的投诉标题、申诉部门、处理状态、发布时间、详情页面中的投诉文本内容以及图片。
【网站地址】:东莞阳光热线问政平台
【完整项目代码】:我的GitHub.

在这里插入图片描述
在这里插入图片描述
【爬取结果】:在这里插入图片描述
以下为本项目的主要模块代码,已在必要的地方做了详细注释:

4.1 爬虫文件
//  spiders/yg.py
import scrapy
import random
from YangGuang.items import YangguangItem

class YgSpider(scrapy.Spider):
    name = 'yg'
    allowed_domains = ['sun0769.com']
    start_urls = ['http://wz.sun0769.com/index.php/question/questionType?type=4&page=0']

    def parse(self, response):
        '''首页解析函数'''
        tr_list = response.xpath('//div[@class="greyframe"]/table[2]//tr')
        headers = {'User-Agent': self.get_ua()}
        for tr in tr_list:
            item = YangguangItem()
            item['title'] = tr.xpath('.//a/@title').extract_first()
            item['department'] = tr.xpath('.//a[@class="t12h"]/text()').extract_first()
            item['status'] = tr.xpath('.//td[3]/span/text()').extract_first()
            item['publish_date'] = tr.xpath('.//td[last()]/text()').extract_first()
            item['href'] = tr.xpath('.//a[@class="news14"]/@href').extract_first()

            yield scrapy.Request(
                url=item['href'],
                callback=self.parse_detail,  # 指定传入的URL由parse_detail解析函数处理
                meta={'item':item},          # 向parse_detail传递的元数据
                headers=headers,             # 传入包含随机UA的headers
            )

        next_url = response.xpath('//div[@class="pagination"]/a[text()=">"]/@href').extract_first()
        if next_url is not None:
            yield scrapy.Request(
                url=next_url,
                callback=self.parse,
            )

    def parse_detail(self, response):
        '''详情页面解析函数'''
        item = response.meta['item']  # 取出从parse传来的元数据
        img = response.xpath('//div[@class="textpic"]/img/@src').extract_first()  # 获取详情页面的图片地址(不含域名)
        if img is None: # 若获取图片地址失败,则1.该页面仅有文本内容;2.该页面不存在-404
            item['img'] = None
            item['contentext'] = response.xpath('//div[@class="wzy1"]//tr[1]/td[@class="txt16_3"]/text()').extract()
        else:   # 若成功获取图片地址,加上域名前缀,且文本内容xpath如下
            item['img'] = 'http://wz.sun0769.com'+img
            item['contentext'] = response.xpath('//div[@class="contentext"]/text()').extract()

        yield item

    def get_ua(self):
        '''随机生成User-Agent用户代理'''
        first_num = random.randint(55, 76)
        third_num = random.randint(0, 3800)
        fourth_num = random.randint(0, 140)
        os_type = [
            '(Windows NT 6.1; WOW64)', '(Windows NT 10.0; WOW64)', '(X11; Linux x86_64)',
            '(Macintosh; Intel Mac OS X 10_14_5)'
        ]
        chrome_version = 'Chrome/{}.0.{}.{}'.format(first_num, third_num, fourth_num)

        ua = ' '.join(['Mozilla/5.0', random.choice(os_type), 'AppleWebKit/537.36',
                       '(KHTML, like Gecko)', chrome_version, 'Safari/537.36']
                      )
        return ua
4.2 管道文件
// pipelines.py
import re
from YangGuang.settings import MONGO_HOST, MONGO_PORT
from pymongo import MongoClient

class YangguangPipeline(object):
    def open_spider(self, spider):
        '''爬虫开始执行时执行的函数(仅run一次),可放入数据库连接代码'''
        client = MongoClient(MONGO_HOST, MONGO_PORT)  # 新建MongoDB客户端实例
        self.col = client['YangGuang']['content']     # 新建数据库为YangGuang,集合为content的实例

    def process_item(self, item, spider):
        item['contentext'] = self.process_content(item['contentext']) # 清洗item中content数据
        self.col.insert_many([dict(item)])     # 向MongoDB中插入数据
        # print(item['href'])
        return item

    def process_content(self, content):
        '''对content数据进行清洗,去除空白字符等不必要的数据'''
        if content == []:  # 如果content是空列表,说明详细页面响应404
            return None
        # 使用列表切片和sub替换,逐个清洗数据中的空格、\t、\n和\xa0等任意空白字符
        content = [re.sub('\s', '', item) for item in content]
        # 将含有多个字段的列表连接成只有一个字段的列表
        content = ''.join(content)
        return content
4.3 项目文件
// items.py
import scrapy

class YangguangItem(scrapy.Item):
    title = scrapy.Field()        # 标题
    department = scrapy.Field()   # 负责部门
    status = scrapy.Field()       # 处理状态
    publish_date = scrapy.Field() # 发布时间
    href = scrapy.Field()         # 详情页面超链接
    contentext = scrapy.Field()   # 详情页面投诉文本内容
    img = scrapy.Field()          # 详情页面投诉图片内容

【参考文献】:
[1] XPath常用语法总结及应用.
[2] 东莞阳光热线问政平台.

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SL_World

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值