Python - 爬虫之Scrapy

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

1、scrapy 概念和流程

一、概念

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

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

二、工做流程

scrapy流程图

其流程描述以下:node

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

注意:python

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

三、各模块的具体做用

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

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

2、scrapy 入门使用

一、安装 scrapy

scrapy 有 2 种安装方式:es6

  • 命令:github

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

    pip/pip3 install scrapy
    复制代码

二、项目开发流程

  1. 建立项目:ajax

    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 能够关闭框架日志信息输出

三、三个内置对象

  • 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:响应状态码

四、入门实战

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
复制代码

3、scrapy 数据建模(items)

一、数据建模

一般在项目开发过程当中,须要在 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种解决办法:
# 一、PyCharm以myspider为根目录从新打开项目
# 二、不以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 文件中注册启用管道

4、scrapy 处理翻页(Request)

一、思路

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

二、实现步骤

  1. 肯定 url 地址

  2. 构造请求

    scrapy.Request(url, callback)
    复制代码
    • callback:指定解析函数名称,表示该请求返回的响应使用哪个函数进行解析
  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'
    复制代码

四、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']
复制代码

5、scrapy 模拟登陆(cookie)

一、模拟登陆的不一样实现方式

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

二、scrapy 直接携带 cookies 获取须要登陆信息的页面

应用场景:

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

一、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

二、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 的传递过程

6、scrapy 管道的使用

一、pipeline 中经常使用的方法

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

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

二、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()
复制代码

三、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):可以在爬虫关闭的时候执行一次
  • 上述俩个方法常常用于爬虫和数据库的交互,在爬虫开启的时候创建和数据库的链接,在爬虫关闭的时候断开和数据库的链接

7、scrapy 中间件

一、介绍

1)分类

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

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

2)做用

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

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

3)比较

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

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

二、使用方法

注意:结合 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 文件中开启中间件,权重值越小越优先执行(同管道注册同样)

三、实现随机 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,
}
复制代码

四、实现 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等文章, 可能有你想要了解的技能知识点哦~

相关文章
相关标签/搜索