阅读 407

Python - 爬虫之Scrapy

欢迎关注微信公众号:FSA全栈行动 👋

一、scrapy 概念和流程

1、概念

Scrapy 是一个 python 编写的,被设计用于爬取网络数据、提取结构性数据的开源网络爬虫框架。

补充:Scrapy 使用了 Twisted 异步网络框架,可以加快下载速度

2、工作流程

scrapy流程图

其流程描述如下:

  1. 爬虫中起始的 url 构造成 request 对象 --> 爬虫中间件 --> 引擎 --> 调度器
  2. 调度器把 request --> 引擎 --> 下载中间件 --> 下载器
  3. 下载器发送请求,获取 response 响应 --> 下载中间件 --> 引擎 --> 爬虫中间件 --> 爬虫
  4. 爬虫提取 url 地址,组装成 request 对象 --> 爬虫中间件 --> 引擎 --> 调度器,重复步骤 2
  5. 爬虫提取数据 --> 引擎 --> 管道处理和保存数据

注意:

  • 图中绿色线条表示数据的传递
  • 图中中间件的位置决定了其作用
  • 引擎处于 4 个模块中间,各个模块之间相互独立,只和引擎进行交互

3、各模块的具体作用

模块作用是否实现
Scrapy Engine(引擎)总指挥:负责数据和信号在不同模块之间传递scrapy 已经实现
Scheduler(调度器)一个队列,存放引擎发过来的 request 请求scrapy 已经实现
Downloader(下载器)下载把引擎发过来的 request 请求,并返回给引擎scrapy 已经实现
Spider(爬虫)处理引擎发来的 response,提取数据、url,并交给引擎需要手写
Item Pipeline(管道)处理引擎传过来的数据,比如存储需要手写
Downloader Middlewares(下载中间件)可以自定义的下载扩展,比如设置代理一般不用手写
Spider Middlewares(爬虫中间件)可以自定义 requests 请求和进行 response 过滤一般不用手写

注意:爬虫中间件 和 下载中间件 只是运行逻辑的位置不同,作用是重复的:如替换 UA 等。

二、scrapy 入门使用

1、安装 scrapy

scrapy 有 2 种安装方式:

  • 命令:

    sudo apt-get install scrapy
    复制代码
  • pip:

    pip/pip3 install scrapy
    复制代码

2、项目开发流程

  1. 创建项目:

    scrapy startproject <项目名称>
    
    eg:
    	scrapy startproject myspider
    复制代码
  2. 生成一个爬虫:

    scrapy genspider <爬虫名字> <允许爬取的域名>
    
    eg:
    	cd myspider
    	scrapy genspider example example.com
    复制代码
    • 爬虫名字:作用爬虫运行时的参数

    • 允许爬取的域名:为对于爬虫设置的爬取范围,设置之后用于过滤要爬取的 url,如果爬取的 url 与允许的域不同则被过滤掉

      注意:执行完命令后,myspider/spiders 目录下,会多出一个 example.py 爬虫文件

  3. 提取数据:

    根据网站结构在 spider 中实现数据采集相关内容

  4. 保存数据:

    使用 pipeline 进行数据后续处理和保存

  5. 运行 scrapy

    scrapy crawl <爬虫名字>
    scrapy crawl <爬虫名字> --nolog
    
    eg:
    	scrapy crawl example
    复制代码

    注意 :需要在项目录下执行命令;--nolog 可以关闭框架日志信息输出

3、三个内置对象

  • request 请求对象:由 url method post_data headers 等构成
  • response 响应对象:由 url body status headers 等构成
  • item 数据对象:本质是字典

1)定位元素以及提取数据

解析并获取 scrapy 爬虫中的数据:利用 xpath 规则字符串进行定位和提取

  • response.xpath():返回一个类似 List 的类型,其中包含的是 selector 对象,操作和列表一样,但是有一些额外方法:
    • extract():返回一个包含有字符串的列表
    • extract_first():返回列表中的第一个字符串,列表为空则返回 None

2)response 响应对象的常用属性

  • response.url:当前响应的 url 地址
  • response.request.url:当前响应对应的请求的 url 地址
  • response.headers:响应头
  • response.request.headers:当前响应的请求头
  • response.body:响应体,也就是 html 代码,byte 类型
  • response.status:响应状态码

4、入门实战

1)创建项目&爬虫

scrapy startproject myspider
cd myspider
scrapy genspider itcast itcast.cn
复制代码

2)完善爬虫(抓取数据)

在/myspider/myspider/spiders/itcast.py 中修改内容如下:

import scrapy

class ItcastSpider(scrapy.Spider):
    name = 'itcast'
    # 2.检查域名
    allowed_domains = ['itcast.cn']
    # 1.修改起始url
    start_urls = ['http://itcast.cn/channel/teacher.shtml']  # 设置起始的url,我们只需要设置就好,通常会被自动的创建成请求发送

    # 3.在parse方法中实现爬取逻辑
    def parse(self, response):
        # 解析方法,通常用于起始url对应响应的解析
        # 定义对于网站的相关操作
        # with open('itcast.html', 'wb') as f:
        #     f.write(response.body)

        # 获取所有老师节点
        node_list = response.xpath('//div[@class="li_txt"]')

        # 遍历老师节点列表
        for node in node_list:
            temp = {}

            # xpath方法返回的是选择器对象列表,extract()用于从选择器对象中提取数据
            # temp['name'] = node.xpath('./h3/text()')[0].extract()
            # temp['title'] = node.xpath('./h4/text()')[0].extract()
            # temp['desc'] = node.xpath('./p/text()')[0].extract()

            # xpath结果为只含有一个值的列表,可以使用extract_first(),如果为多个值则使用extract()
            temp['name'] = node.xpath('./h3/text()').extract_first()
            temp['title'] = node.xpath('./h4/text()').extract_first()
            temp['desc'] = node.xpath('./p/text()').extract_first()

            # {'name': [<Selector xpath='./h3/text()' data='黄老师'>], 'title': [<Selector xpath='./h4/text()' data='高级讲师'>], 'desc': [<Selector xpath='./p/text()' data='15年+的软件开发与教学经验,参与中国联通VOP系统,中国联通在线计费系统...'>]}
            # extract()之后:
            # {'name': '黄老师', 'title': '高级讲师', 'desc': '15年+的软件开发与教学经验,参与中国联通VOP系统,中国联通在线计费系统,道路交通事故救助基金管理系统等的开发工作;\r\n拥有丰富的教学经验。'}
            print(temp)

            yield temp
复制代码

注意:

  • scrapy.Spider爬虫类中必须有名为 parse 的解析
  • 如果网站结构层次比较复杂,也可以自定义其他解析函数
  • 在解析函数中提取的 url 地址如果要发送请求,则必须属于 allowed_domains 范围内,但是 start_urls 中的 url 地址不受这个限制
  • parse() 函数中使用 yield 返回数值。
  • yield 能够传递的对象只能是:BaseItem,Request,dict,None

3)完善管道(保存数据)

  1. 定义一个管道类
  2. 重写管道类的 process_item() 方法
  3. process_item() 方法处理完 item 之后必须返回给引擎

这里直接使用创建项目时默认创建好的管道类:/myspider/myspider/pipelines/MyspiderPipeline,修改内容如下:

import json


class MyspiderPipeline:

    def __init__(self):
        self.file = open('itcast.json', 'w')

    def process_item(self, item, spider):
        # print('item:', item)
        # 将字典数据序列化
        json_data = json.dumps(item, ensure_ascii=False) + ',\n'

        # 将数据写入文件
        self.file.write(json_data)

        # 默认使用完管道之后需要将数据返回给引擎
        return item

    def __del__(self):
        self.file.close()
复制代码

4)启用管道

ITEM_PIPELINES = {
   'myspider.pipelines.MyspiderPipeline': 300,
   # 'myspider.pipelines.MyspiderPipelineOther': 301,
}
复制代码
  • 配置项中,键为使用的管道类,管道类引用声明使用.进行分割,分别为:项目目录.文件.管理类
  • 配置项中,值为管道的使用顺序,设置的数值越小越先执行,该值一般设置在 1000 以内。

5)运行爬虫

在控制台运行如下命令即可运行爬虫:

scrapy crawl itcast
scrapy crawl itcast --nolog
复制代码

三、scrapy 数据建模(items)

1、数据建模

通常在项目开发过程中,需要在 items.py 中进行数据建模

1)为什么要建模

  • 定义 item 即提前规划好哪些字段需要抓取,防止手误,因为定义好之后,在运行过程中,系统会自动检查
  • 配置注释一起可以清晰的知道要抓取哪些字段,没有定义的字段不能抓取,在目标字段少的时候可以使用字典代替
  • 使用 scrapy 的一些特定组件中需要 Item 做支持,如 scrapy 的 ImagesPipeline 管道类。

2)如何建模

items.py 文件中定义发提取的字段:

import scrapy

class MyspiderItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()  # 讲师的名字
    title = scrapy.Field()  # 讲师的职称
    desc = scrapy.Field()  # 讲师的介绍

# if __name__ == '__main__':
#     item = MyspiderItem()
#     item['name'] = 'lqr'  # ok
#     item['nama'] = 'lqr'  # KeyError: 'MyspiderItem does not support field: nama'
复制代码

注意:scrapy.Item 可以理解为一个更高级的 “字典”,可以对键名进行限制、校验。但切记它不是字典,如果你需要对字典进行操作,可以使用 dict()scrapy.Item 进行强制转换。

3)使用模板类

模板类定义以后,需要在爬虫中导入并且实例化,之后的使用方法和字典相同:

# 如果报包报错有2种解决办法:
# 1、PyCharm以myspider为根目录重新打开项目
# 2、不以myspider为根目录的话,可以右键myspider-->Make Directory as-->Sources Root
from myspider.items import MyspiderItem

    def parse(self, response):
        item = MyspiderItem()
        ...
        item['name'] = node.xpath('./h3/text()').extract_first()
        item['title'] = node.xpath('./h4/text()').extract_first()
        item['desc'] = node.xpath('./p/text()').extract_first()
		...
        yield item
复制代码

4)开发流程总结

  1. 创建项目

    scrapy startproject 项目名
    复制代码
  2. 明确目标

    • 在 items.py 文件中进行建模
  3. 创建爬虫

    • 创建爬虫

      scrapy genspider 爬虫名 允许的域
      复制代码
    • 编写爬虫

      修改start_urls
      检查修改allowed_domains
      编写解析方法
      复制代码
  4. 保存数据

    • pipelines.py 文件中定义对数据处理的管道
    • settings.py 文件中注册启用管道

四、scrapy 处理翻页(Request)

1、思路

  1. 找到下一页的 url 地址
  2. 构造 url 地址的请求对象,传递给引擎

2、实现步骤

  1. 确定 url 地址

  2. 构造请求

    scrapy.Request(url, callback)
    复制代码
    • callback:指定解析函数名称,表示该请求返回的响应使用哪一个函数进行解析
  3. 把请求交给引擎

3、实战(网易招聘爬虫)

1)思路分析

  1. 获取首页数据
  2. 寻找下一页地址,进行翻页,获取数据

2)代码实现

  • 创建项目
scrapy startproject wangyi

cd wangyi
scrapy gespider job 163.com
复制代码
  • 定义模板类
import scrapy


class WangyiItem(scrapy.Item):
    # define the fields for your item here like:
    name = scrapy.Field()
    link = scrapy.Field()
    depart = scrapy.Field()
    category = scrapy.Field()
    type = scrapy.Field()
    address = scrapy.Field()
    num = scrapy.Field()
    date = scrapy.Field()
复制代码
  • 编写爬虫(抓取数据)
import scrapy
from wangyi.items import WangyiItem


class JobSpider(scrapy.Spider):
    name = 'job'
    # 2.检查修改allowed_domains
    allowed_domains = ['163.com']
    # 1.修改start_urls
    start_urls = ['https://hr.163.com/position/list.do']

    def parse(self, response):
        # 提取数据
        # 获取所有职位节点列表
        node_list = response.xpath('//*[@class="position-tb"]/tbody/tr')

        # 遍历节点列表
        for num, node in enumerate(node_list):
            # 设置过滤条件,将目标节点获取出来
            if num % 2 == 0:
                item = WangyiItem()
                item['name'] = node.xpath('./td[1]/a/text()').extract_first()
                # item['link'] = 'https://hr.163.com' + node.xpath('./td[1]/a/@href').extract_first()
                # response.urljoin()用于拼接相对路径的url,可以理解为自动补全
                item['link'] = response.urljoin(node.xpath('./td[1]/a/@href').extract_first())
                item['depart'] = node.xpath('./td[2]/text()').extract_first()
                item['category'] = node.xpath('./td[3]/text()').extract_first()
                item['type'] = node.xpath('./td[4]/text()').extract_first()
                item['address'] = node.xpath('./td[5]/text()').extract_first()
                item['num'] = node.xpath('./td[6]/text()').extract_first().strip()
                item['date'] = node.xpath('./td[7]/text()').extract_first()
                # print(item)
                yield item

        # 模拟翻页
        part_url = response.xpath('/html/body/div[2]/div[2]/div[2]/div/a[last()]/@href').extract_first()
        # 判断终止条件
        if part_url != 'javascript:void(0)':
            next_url = response.urljoin(part_url)
            # 构建请求对象,并且返回给引擎
            yield scrapy.Request(
                url=next_url,
                callback=self.parse
            )
复制代码

注意:引擎根据爬虫 yield 的对象类型,将对象分配给对应的模块处理,比如:如果是模板类对象或字典,则会交给管道(Item Pipeline);如果是 Request 对象,则会交给队列(Scheduler)。

  • 编写管道(存储数据)
import json


class WangyiPipeline:

    def __init__(self):
        self.file = open('wangyi.json', 'w')

    def process_item(self, item, spider):
        item = dict(item)

        str_data = json.dumps(item, ensure_ascii=False) + ',\n'

        self.file.write(str_data)

        return item

    def __del__(self):
        self.file.close()
复制代码
  • 启用管道
ITEM_PIPELINES = {
    'wangyi.pipelines.WangyiPipeline': 300,
}
复制代码
  • 运行爬虫
scrapy crawl job --nolog
复制代码

3)扩展:

  • 可以在 settings 中设置 ROBOTS 协议

    # False表示忽略网站的robots.txt协议,默认为True
    ROBOTSTXT_OBEY = False
    复制代码
  • 可以在 settings 中设置 User-Agent:

    # scrapy发送的每一个请求的默认UA都是设置的这个User-Agent
    USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36'
    复制代码

4、scrapy.Request 的更多参数

scrapy.Request(url[, callback, method="GET", headers, body, cookies, meta, dont_filter=False])
复制代码

注意:中括号[]里的参数为可选参数

参数解释:

  • callback:表示当前 url 的响应交给哪个函数去处理
  • meta:实现数据在不同的解析函数中传递,meta 默认带有部分数据,比如下载延迟,请求深度等
  • dont_filter:默认为 False,会过滤请求的 url 地址,即请求过的 url 地址不会继续被请求,对需要重复请求的 url 地址可以把它设置为 True,比如贴吧的翻页请求,页面的数据总是在变化,start_urls 中的地址会被反复请求,否则程序不会启动
  • method:指定 POST 或 GET 请求
  • headers:接收一个字典,其中不包括 cookies
  • cookies:接收一个字典,专门放置 cookies
  • body:接收 json 字符串,为 POST 的数据,发送 payload_post 请求时使用

1)meta 参数的使用

  • meta 的使用:

    • meta 可以实现数据在不同的解析函数中传递
  • meta 的注意事项:

    • meta 参数是一个字典
    • meta 字典中有一个固定的键 proxy,表示代理 ip
  • 使用举例:

def parse(self, response):
    ...
    yield scrapy.Request(detail_url, callback=self.parse_detail, meta={'item': item})
    ...


def parse_detail(self,response):
    # 获取之前传入的item
    item = response.meta['item']
复制代码

五、scrapy 模拟登录(cookie)

1、模拟登录的不同实现方式

  • requests 模块
    • 直接携带 cookies 请求页面
    • 找到 url 地址,发送 post 请求存储 cookie
  • selenium(浏览器自动处理 cookie)
    • 找到对应的 input 标签,输入文本点击登录
  • scrapy
    • 直接携带 cookies
    • 找 url 地址,发送 post 请求存储 cookie

2、scrapy 直接携带 cookies 获取需要登录信息的页面

应用场景:

  • cookie 过期时间很长,常见于一些不规范的网站
  • 能在 cookie 过期之前把所有的数据拿到
  • 配合其他程序使用,比如其使用 selenium 把登录之后的 cookie 获取到并保存到本地,scrapy 发送请求之前先读取本地 cookie

1、start_url 携带 cookie(重写 start_requests 方法)

scrapy 中 start_url 是通过 start_requests 来进行处理的,可能通过重写该方法让 start_url 携带上请求头信息,实现代码如下:

import scrapy


class Git1Spider(scrapy.Spider):
    name = 'git1'
    allowed_domains = ['github.com']
    start_urls = ['https://github.com/GitLqr']

    def start_requests(self):
        """
        重写start_requests,发送携带cookies的Request。
        默认start_requests只是普通的get请求,不会携带自定义的头信息
        """
        url = self.start_urls[0]

        temp = '_octo=GH1.1.1045146750.1615451260; _device_id=cd8d64981fcb3fd4ba7f587873e97804'
        # 把cookies字符串转成字典
        cookies = {data.split('=')[0]: data.split('=')[-1] for data in temp.split('; ')}

        yield scrapy.Request(
            url=url,
            callback=self.parse,
            cookies=cookies
        )

    def parse(self, response):
        print(response.xpath('/html/head/title/text()').extract_first())
复制代码

注意:

  • scrapy 中 cookie 不能够放在 headers 中,在构造请求的时候有专门的 cookies 参数,能够接收字典形式的 cookie
  • 可能需要在 settings 中设置 ROBOTS 协议、USER_AGENT

2、scrapy.Request 发送 post 请求

scrapy.Request 发送 post 请求有两种方式:

  • 通过 scrapy.Request() 指定 method、body 参数来发送 post 请求(不推荐)

  • 使用 scrapy.FormRequest() 来发送 post 请求(推荐)

注意:scrapy.FormRequest() 能够发送表单和 ajax 请求,参考阅读 www.jb51.net/article/146…

举例:

import scrapy


class Git2Spider(scrapy.Spider):
    name = 'git2'
    allowed_domains = ['github.com']
    start_urls = ['http://github.com/login']

    def parse(self, response):
        username = 'GitLqr'
        password = 'balabala'

        # 从登录页面响应中解析出post数据
        token = response.xpath('//input[@name="authenticity_token"]/@value').extract_first()

        post_data = {
            'commit': 'Sign in',
            'authenticity_token': token,
            'login': username,
            'password': password,
            'webauthn-support': 'supported',
        }
        print(post_data)

        # 针对登录url发送post请求
        yield scrapy.FormRequest(
            url='https://github.com/session',
            callback=self.after_login,
            formdata=post_data
        )

    def after_login(self, response):
        yield scrapy.Request('https://github.com/GitLqr', callback=self.check_login)

    def check_login(self, response):
        print(response.xpath('/html/head/title/text()').extract_first())
复制代码

注意:在 settings.py 中通过设置 COOKIES_DEBUG = True 能够在终端查看到 cookie 的传递过程

六、scrapy 管道的使用

1、pipeline 中常用的方法

  • process_item(self, item, spider)
    • 管道类中必须有的函数
    • 实现对 item 数据的处理
    • 必须 return item
  • open_spider(self, spider):在爬虫开启的时候仅执行一次
  • close_spider(self, spider):在爬虫关闭的时候仅执行一次

注意:以上 3 个方法,可以通过 spider.name 获取爬虫的名字

2、pipeline 判断数据来源

scrapy 的 Item Pipeline 模块可以有多个管道,当有一个 spider 把数据对象通过引擎交给 Item Pipeline 模块时, Item Pipeline 模块中的所有管道会按 settings.py 中指定的管道顺序一一被执行。但很多时候,我们需要管道针对特定爬虫做数据存储的,这时就需要在管道中对数据对象的来源做判断。

注意:不同的 pipeline 能够对一个或多个爬虫进行不同的数据处理的操作,比如一个进行数据清洗,一个进行数据的保存

举例:

  • 爬虫 1:job.py
import scrapy
from wangyi.items import WangyiItem


class JobSpider(scrapy.Spider):
    name = 'job'
    ...

    def parse(self, response):
        ...
        yield item
复制代码
  • 爬虫 2:job_simple.py
import scrapy
from wangyi.items import WangyiSimpleItem


class JobSimpleSpider(scrapy.Spider):
    name = 'job_simple'
    ...

    def parse(self, response):
        ...
        yield item
复制代码
  • 2 个管道:pipelines.py
class WangyiPipeline:

    def open_spider(self, spider):
        if spider.name == 'job':
            self.file = open('wangyi.json', 'w')

    def process_item(self, item, spider):
        if spider.name == 'job':
            item = dict(item)
            str_data = json.dumps(item, ensure_ascii=False) + ',\n'
            self.file.write(str_data)
        return item

    def close_spider(self, spider):
        if spider.name == 'job':
            self.file.close()


class WangyiSimplePipeline:

    def open_spider(self, spider):
        if spider.name == 'job_simple':
            self.file = open('wangyi_simple.json', 'w')

    def process_item(self, item, spider):
        if spider.name == 'job_simple':
            item = dict(item)
            str_data = json.dumps(item, ensure_ascii=False) + ',\n'
            self.file.write(str_data)
        return item

    def close_spider(self, spider):
        if spider.name == 'job_simple':
            self.file.close()
复制代码

3、pipeline 使用注意点

  • 使用之前需要在 settings 中开启
  • pipeline 在 settings 中键表示位置(即 pipeline 在项目中的位置可以自定义),值表示距离引擎的远近,越近数据会越先经过:权重值小的优先执行
  • 有多个 pipeline 的时候,process_item 的方法必须 return item,否则后一个 pipeline 取到的数据为 None 值
  • pipeline 中 process_item 方法必须要有,否则 item 没有办法接收和处理
  • process_item 方法接收 item 和 spider,其中 spider 表示当前传递 item 过来的 spider
  • open_spider(spider):能够在爬虫开启的时候执行一次
  • close_spider(spider):能够在爬虫关闭的时候执行一次
  • 上述俩个方法经常用于爬虫和数据库的交互,在爬虫开启的时候建立和数据库的连接,在爬虫关闭的时候断开和数据库的连接

七、scrapy 中间件

1、介绍

1)分类

根据 scrapy 运行流程中所在位置不同,对 scrapy 中间件进行分类:

  • 下载中间件
  • 爬虫中间件

2)作用

scrapy 中间件的作用是:预处理 request 和 response 对象

  • 对 header 以及 cookie 进行更换和处理
  • 使用代理 ip 等
  • 对请求进行定制化操作

3)比较

  • 默认情况下,两种中间件都在 middlewares.py 一个文件中

  • 爬虫中间件使用方法和下载中间件相同,且功能重复,通常使用下载中间件

2、使用方法

注意:结合 scrapy 流程图可方便理解

scrapy流程图

中间件使用步骤如下:

  1. middlerware.py 中定义中间件类

  2. 在中间件类中,重写处理请求或者响应的方法(以Downloader Middlewares为例)

    • process_request(self, request, spider)

      当每个 request 通过下载中间件时,该方法被调用【Scrapy Engine --> Downloader】

      • 返回 None【继续】:该 request 对象通过引擎传递给其他权重低的 process_request 方法,如果所有中间件都返回 None,则请求最终被交给下载器处理。注意:没有 return 也是返回 None。
      • 返回 Request 对象【中断】:把 request 对象通过引擎交给调度器,此时将不通过其他权重低的 process_request 方法
      • 返回 Response 对象【中断】:请求不会达到下载器,会直接把 response 通过引擎交给 Spider 进行解析
    • process_response(self, request, response, spider)

      当下载器完成 http 请求,传递响应给引擎的时候调用【Scrapy Engine <-- Downloader】

      • 返回 Request 对象【中断】:通过引擎交给调度器继续请求,此时将不通过其他权重低的 process_response 方法
      • 返回 Response 对象【继续】:通过引擎交给爬虫处理或交给权重更低的其他下载中间件的 process_response 方法
  3. settings 文件中开启中间件,权重值越小越优先执行(同管道注册一样)

3、实现随机 User-Agent 下载中间件

1)准备 UA 列表

settings.py 文件中定义一个存放了大量 UA 的列表 USER_AGENT_LIST

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36'

USER_AGENT_LIST = [
    'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50',
    'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0;',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)',
    'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1',
    'Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; en) Presto/2.8.131 Version/11.11',
    'Opera/9.80 (Windows NT 6.1; U; en) Presto/2.8.131 Version/11.11',
    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; The World)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SE 2.X MetaSr 1.0; SE 2.X MetaSr 1.0; .NET CLR 2.0.50727; SE 2.X MetaSr 1.0)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Avant Browser)',
    'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)',
    'Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5',
    'Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
    'MQQBrowser/26 Mozilla/5.0 (Linux; U; Android 2.3.7; zh-cn; MB200 Build/GRJ22; CyanogenMod-7) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1',
    'Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/build-1107180945; U; en-GB) Presto/2.8.149 Version/11.10',
    'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13',
    'Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+',
    'Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0',
    'Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124',
    'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)',
    'UCWEB7.0.2.37/28/999',
    'NOKIA5700/ UCWEB7.0.2.37/28/999',
    'Openwave/ UCWEB7.0.2.37/28/999',
    'Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999',
    'UCWEB7.0.2.37/28/999',
    'NOKIA5700/ UCWEB7.0.2.37/28/999',
    'Openwave/ UCWEB7.0.2.37/28/999',
    'Mozilla/4.0 (compatible; MSIE 6.0; ) Opera/UCWEB7.0.2.37/28/999'
]
复制代码

注意:该 USER_AGENT_LIST 变量名可以自定义,也可以写在其他 py 文件中,写在 settings.py 文件中,只是为了规范而已。

2)定义下载中间件

middlewares.py 文件中定义随机 UA 的下载中间件:

import random

from Douban.settings import USER_AGENT_LIST


class RandomUserAgentMiddleware(object):
    def process_request(self, request, spider):
        # print(request.headers['User-Agent'])
        ua = random.choice(USER_AGENT_LIST)
        request.headers['User-Agent'] = ua
复制代码

3)启用下载中间件

settings.py 文件中配置开启自定义的下载中间件:

DOWNLOADER_MIDDLEWARES = {
    'Douban.middlewares.RandomUserAgentMiddleware': 543,
}
复制代码

4、实现 Selenium 渲染的下载中间件

scrapy 的 Downloader 模块只会根据请求获取响应,但实际开发过程中,有些页面上的数据是通过 ajax 延迟加载出来的,Downloader 模块无法应对这种情况,这时就需要用到 Selenium 来处理这类请求,等页面渲染完成后,再把渲染好的页面返回给爬虫即可:

from selenium import webdriver
from scrapy.http import HtmlResponse


class SeleniumMiddleware(object):
    def __init__(self):
        self.driver = webdriver.Chrome()

    def process_request(self, request, spider):
        url = request.url

        # 过滤需要使用selenium渲染的request
        if 'daydata' in url:
            self.driver.get(url)
            time.sleep(3)
            data = self.driver.page_source
            self.driver.close()

            # 返回HtmlResponse,请求不会达到Downloader,而是直接通过引擎交给爬虫
            response = HtmlResponse(url=url, body=data, encoding='utf-8', request=request)
            return response

    def __del__(self):
        self.driver.quit()
复制代码

如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有Android技术, 还有iOS, Python等文章, 可能有你想要了解的技能知识点哦~

文章分类
后端