【Python】Scrapy 框架

198 阅读13分钟

Scrapy是什么?

Scrapy 是用 Python 实现的一个为了爬取网站数据、提取结构性数据而编写的应用框架(异步爬虫框架)。

通常我们可以很简单的通过 Scrapy 框架实现一个爬虫,抓取指定网站的内容或图片。 Scrapy使用了Twisted异步网络框架,可以加快我们的下载速度。

  • 异步和非阻塞的区别

image.png

异步:调用在发出之后,这个调用就直接返回,不管有无结果;

非阻塞:关注的是程序在等待调用结果时的状态,指在不能立刻得到结果之前,就调用不会阻塞当前线程;

Scrapy的优势:

爬虫必备的技术

  • 能够使我们的爬虫程序更加稳定 效率更高(多线程)
  • 配置和可扩展性非常强(很灵活)
  • downloader 下载器(基于多线程的) 发送请求 获取响应的。

Scrapy参考学习

scrapy官方学习网址:scrapy-chs.readthedocs.io/zh_CN/1.0/i…

最新的:docs.scrapy.org/en/latest/

Scrapy的安装:

pip install scrapy==2.5.1 -i <https://pypi.tuna.tsinghua.edu.cn/simple>

安装后运行 scrapy 时可能会遇到的问题 & 解决方案:

问题一:

image.png 报错内容:AttributeError: module 'OpenSSL.SSL' has no attribute 'SSLv3_METHOD'

【原因】pyopenssl的版本问题
【解决方案】卸载当前的高版本,安装低版本
pip uninstall pyopenssl
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyopenssl==22.0.0

问题二:

报错内容: AttributeError: module ‘lib‘ has no attribute ‘OpenSSL_add_all_algorithms

【原因】cryptography版本过高,我的当前版本是44.0
【解决方案】卸载当前的高版本,安装低版本
pip uninstall cryptography
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple cryptography==36.0.2

注意:降低版本后,需要重新创建项目才生效。

各个组件的功能介绍

image.png

1. 引擎(engine) 【scrapy已经实现】

Scrapy 引擎是整个框架的核心。它用来控制调试器、下载器、爬虫。实际上,引擎相当于计算机的CPU,它控制着整个流程。

2. 调度器(scheduler)【scrapy已经实现】

本质上这东西可以看成是一个队列,里面存放着一堆我们即将要发送的请求,可以看成是一个url的容器 它决定了下一步要去爬取哪一个url,同时去除重复的网址(不做无用功)。

3. 下载器(downloader) 【scrapy已经实现】

是所有组件中负担最大的,它用于高速地下载网络上的资源。Scrapy的下载器代码不会太复杂,但效率高,主要的原因是Scrapy下载器是建立在twisted这个高效的异步模型上的(其实整个框架都在建立在这个模型上的)。

它的本质就是用来发动请求的一个模块,可以把它理解成是一个requests.get()的功能,只不过这货返回的是一个response对象。

4. 爬虫(spider) 【需要手写】

负责解析下载器返回的 response 对象,从中提取到目标数据,即所谓的实体(Item)。也可以从中提取出链接,让Scrapy继续抓取下一个页面,即翻页。

5. 管道(Item pipeline) 【需要手写】

用于处理爬虫(spider)提取的实体。主要的功能是持久化实体、验证实体的有效性、清除不需要的信息。

6. 下载中间件(downloader Middlewares) 【一般不用手写】

可以自定义的下载扩展 比如设置代理 处理引擎与下载器之间的请求与响应(用的比较多)

7. 爬虫中间件(Spider Middlewares) 【一般不用手写】

可以自定义requests请求和进行response过滤(处理爬虫程序的响应和输出结果以及新的请求)

爬虫方式:

第一种爬虫方式:

image.png

第二种爬虫方式:

image.png

Scrapy 的工作流程:

本图按顺序说明整个程序执行时候发生的顺序。

注意:在调用下载器时,往往有一个下载器中间件,使下载速度提速。

image.png

(图一,来源网络,侵删)

image.png

(图二)

Scrapy 入门:

1、切换路径

cd 复制绝对路径

2、创建 Scrapy 项目:

scrapy startproject 项目名称

3、进入项目里面

cd 项目名称

4、创建爬虫程序:

scrapy genspider 程序名字 example.com

example:爬虫程序的名字(不固定的)
example.com:可以允许爬取的范围(不固定的) 是根据你的目标url来指定的 其实很重要 后面是可以修改的

# 举例:
scrapy genspider bd baidu.com

5、执行爬虫程序

方式一:

scrapy crawl 程序名字

方式二:可以通过 start.py 文件执行爬虫项目:

from scrapy import cmdline
cmdline.excute("scrapy crawl 程序名字".split())

6、关于 setting 中的设置项:

BOT_NAME:项目名

ROBOTSTXT_OBEY:是否遵循机器人协议,默认是true,需要改为false,否则很多东西爬不了

CONCURRENT_REQUESTS:最大并发数,很好理解,就是同时允许开启多少个爬虫线程,(default: 16)

DOWNLOAD_DELAY:下载延迟时间,单位是秒,控制爬虫爬取的频率,根据你的项目调整,不要太快也不要太慢,默认是3秒,即爬一个停3秒,设置为1秒性价比较高,如果要爬取的文件较多,写零点几秒也行

COOKIES_ENABLED:是否保存COOKIES,默认关闭,开机可以记录爬取过程中的COKIE,非常好用的一个参数

DEFAULT_REQUEST_HEADERS:默认请求头, 需要在里面添加 USER_AGENT:默认是注释的,这个东西非常重要,如果不写很容易被判断为电脑。

ITEM_PIPELINES:项目管道,300为优先级,越低越爬取的优先度越高。

Scrapy 小结:

scrapy 的核心思想:解耦。

scrapy 其实就是把我们平时写的爬虫进行了四分五裂式的改造,对每个功能进行了单独的封装,并且,各个模块之间互相的不做依赖。一切都由引擎进行调配. 这种思想希望你能知道–解耦,让模块与模块之间的关联性更加的松散,这样我们如果希望替换某一模块的时候会非常的容易,对其他模块也不会产生任何的影响。

来看个实例🌰:

  1. 在控制台中创建项目:cd 根目录
  2. 创建项目:scrapy startproject 项目名称
  3. 进入项目中:cd 项目名称
  4. 创建爬虫程序:scrapy genspider 程序名字 网站.com
  5. 创建执行爬虫的py文件:start.py 【from scrapy import cmdline cmdline.excute("scrapy crawl 程序名字".split())】
  6. 在创建好的项目中,配置 setting 文件:修改君子协议、释放默认的请求头并添加 User-Agent 等信息、打开管道用于持久化存储文件;
  7. 可以添加一个文本文件,用于记录需求和分析等内容;
  8. 然后在爬虫程序的文件中,开始数据解析、获取目标数据等;
  9. 将获取的目标数据返回给管道,管道中进行数据接收、并写入文件中即可。

爬虫程序中,获取目标数据:

image.png

实例化字段域:

image.png

管道接收数据,并写入文件:

image.png

items的使用:

items 文件主要用于定义储存爬取到的数据的数据结构,方便在爬虫和 Item Pipeline 之间传递数据。

pipline的使用:

管道文件 pipelines.py 主要用来对抓取的数据进行处理:一般一个类即为一个管道,比如创建存入MySQL、MangoDB 的管道类。管道文件中 process_item() 方法即为处理所抓数据的具体方法。

pipelines常用方法:

process_item(self,item,spider):处理爬虫抓取的具体数据,在 process_item() 函数中 必须要 return item,因为存在多管道时,会把此函数的返回值继续交由下一个管道继续处理;

open_spider():爬虫项目启动时只执行一次,一般用于数据库连接;

close_spider():爬虫项目结束时只执行一次,一般用于收尾工作,如数据库的关闭。

如何处理翻页:

思路: 1、找到下一页的地址 2、构造一个关于下一页的 url 地址的 request 请求传递给调度器;

scrapy.Request知识点:

scrapy.Request(
    url,
    callback=None,  #  指定传入URL,交给那个解析函数去处理
    method='GET',   
    headers=None,
    body=None,
    cookies=None,
    meta=None,      # 实现不同的解析函数中传递数据,meta默认会携带部分信息,比如下载延迟,请求深度
    encoding='utf-8',
    priority=0,
    dont_filter=False,   # 让scrapy的去重不会过滤当前URL,scrapy默认有url去重功能,对需要重复请求的url有重要用途
    errback=None,
    flags=None,
)

scrapy 下载中间件:

下载中间件,是scrapy提供用于在爬虫过程中可修改Request和Response,用于扩展scrapy的功能 使用方法:

  • 编写一个Download Middlewares和我们编写一个pipeline一样,定义一个类,然后再settings中开启

Download Middlewares默认方法:

处理请求:

process_request(self,request,spider):
    当每个request通过下载中间件时,该方法被调用

处理响应:

process_response(self,request,response,spider):
    当下载器完成http请求,传递响应给引擎的时候调用

当每个Request对象经过下载中间件时会被调用,优先级越高的中间件,越先调用;

该方法应该返回以下对象:

  • None:scrapy会继续执行其他中间件相应的方法;
  • Response对象:scrapy不会再调用其他中间件的process_request方法,也不会去发起下载,而是直接返回该Response对象;
  • Request对象:scrapy不会再调用其他中间件的process_request()方法,而是将其放置调度器待调度下载;
  • 抛出IgnoreRequest异常:会调用process_exception方法;

process_response(request,response,spider)

当每个Response经过下载中间件会被调用,优先级越高的中间件,越晚被调用,与process_request()相反;该方法返回以下对象:

  • Response对象:scrapy会继续调用其他中间件的process_response方法;
  • Request对象:停止中间器调用,将其放置到调度器待调度下载;
  • 抛出IgnoreRequest异常:Request.errback会被调用来处理函数,如果没有处理,它将会被忽略且不会写进日志。

中间件工作流程:

下载中间件的工作原理如下:

  1. 当 Scrapy 引擎收到需要下载的请求时,会将请求发送给下载中间件。
  2. 下载中间件接收到请求后,可以对请求进行修改,比如添加 headers、代理等。
  3. 修改后的请求被发送到目标服务器,目标服务器返回响应数据。
  4. 下载中间件接收到响应数据后,可以对响应进行修改,比如解密、解压缩、修改编码等。
  5. 修改后的响应被返回给 Scrapy 引擎,引擎会继续处理响应数据。

image.png

# 自定义下载中间件
# 导入随机UA的库
import random
from fake_useragent import UserAgent

class UADownloaderMiddleware:
    def process_request(self, request, spider):
        ua = UserAgent()
        user_agent = ua.random
        request.headers['User-Agent'] = user_agent

注意: 在settings中开启当前中间件 
DOWNLOADER_MIDDLEWARES = {
   # 'mw.middlewares.MwDownloaderMiddleware': 543,
   'mw.middlewares.UADownloaderMiddleware': 543,
}

爬虫程序.py
class UaSpider(scrapy.Spider):
    name = 'ua'
    allowed_domains = ['httpbin.org']
    start_urls = ['http://httpbin.org/user-agent']

    def parse(self, response, **kwargs):
        print(response.text)

        # 每次不止生成一个
        yield scrapy.Request(
            url=self.start_urls[0],
            callback=self.parse,
            dont_filter=True   # 对重复数据不进行过滤
        )

Scrapy下载图片:

分为 普通方式下载图片 和 内置方式下载图片:

普通方式下载图片:

参考 汽车之家 案例

内置方式下载图片:

使用images pipeline下载文件步骤: 1、定义好一个 item,然后在这个 item 中定义2个属性,分别是 image_urls 和 images。

image_urls 是用来存储需要下载的文件的 url 链接,需要给一个列表;

2、当文件下载完成后,会把文件下载的相关信息存储到 item 的 images 属性中。如下载路径、下载的 url 和图片校验码等;

3、在配置文件 settings.py 中配置 IMAGES_STORE,这个配置用来设置文件的下载路径; 4、启动 pipeline:在 ITEM_PIPELINES 中设置 scrapy.pipelines.images.ImagesPipeline:1

# items.py文件
import scrapy


class MaterialItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    image_urls = scrapy.Field()
    images = scrapy.Field()
    pass


# settings.py文件
import os

path = os.path.dirname(__file__)   # 查看当前文件路径
path2 = os.path.join(path, 'img_builtIn_methods')  # 内置方法
print(path2)  # /material/material/img_builtIn_methods

# 设置保存地址
IMAGES_STORE = path2

# 爬虫文件
# 内置方法下载  src的列表
# 使用内置方法,需要安装pillow,pip install pillow
item['image_urls'] = [img_src]

yield item

规则式爬虫(深度爬虫):

CrawlSpider其实是Spider的一个子类,除了继承到Spider的特性和功能外,还派生除了其自己独有的更加强大的特性和功能。

Spider是所有爬虫的基类,其设计原则只是为了爬取start_url列表中网页,但是如果从爬取到的网页中提取出的url进行继续的爬取工作使用CrawlSpider更合适。

CrawlSpider使用:

  • 1 创建scrapy工程:scrapy startproject 项目名

  • 2 创建爬虫文件:scrapy genspider -t crawl 爬虫程序文件名 www.xxx.com

    • 此指令对比以前的指令多了 "-t crawl",表示创建的爬虫文件是基于CrawlSpider这个类的,而不再是Spider这个基类。

CrawlSpider的相关参数:

crawlspider的独特属性:

  • rules: 是Rule对象的集合,用于匹配目标网站并排除干扰。该属性为一个正则表达式集合,用于告知爬虫需要跟踪哪些链接
rules = (
    # 参数1:指定链接提取器,设置提取链接的规则(正则表达式)
    # 参数2:指定规则解析器解析数据的规则(回调函数)
    # 参数3:是否将链接提取器继续作用到链接提取器提取出的链接网页中;当callback为none,参数3的默认值为true
    # follow:自己回调自己
    Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True),
)

image.png

LinkExtractor(链接提取器) 参数: 几个参数: link_extractor callback=None cb_kwargs=None follow=None process_links=None process_request=None

link_extractor主要参数为:

  • allow:设置允许提取的url 满足括号中“正则表达式”的值会被提取,如果为空,则全部匹配。
  • deny:设置不允许提取的url(优先级比allow高)与这个正则表达式(或正则表达式列表)不匹配的URL一定不提取。
  • allow_domains:设置允许提取url的域 会被提取的链接的domains。
  • deny_domains:设置不允许提取url的域(优先级比allow_domains高) 一定不会被提取链接的domains。
  • unique=True, :如果出现多个相同的url只会保留一个
  • strip=True :默认为True,表示自动去除url首尾的空格
  • restrict_xpaths:使用xpath表达式,和allow共同作用过滤链接。还有一个类似的restrict_css

CrawlSpider整体爬取流程:

a) 爬虫文件首先根据起始url,获取该url的网页内容

b) 链接提取器会根据指定提取规则将步骤a中网页内容中的链接进行提取

c) 规则解析器会根据指定解析规则将链接提取器中提取到的链接中的网页内容根据指定的规则进行解析

d) 将解析数据封装到item中,然后提交给管道进行持久化存储。

模拟登录的方式:

  • 1 携带 cookie 直接向目标 url 发起请求;
  • 2 携带 data 向目标 url 发送 post 请求,data 里面一般会有账号和密码(js逆向 之后会学习到)
  • 3 通过 selenium 实现模拟登录(会遇到验证码 这个验证码的处理也是之后要进行处理的)

scrapy 中 COOKIES_ENABLED 设置:

  • 1 当COOKIES_ENABLED是注释的时候scrapy默认没有开启cookie;
  • 2 当COOKIES_ENABLED没有注释设置为False的时候scrapy默认使用了settings里面的cookie;
  • 3 当COOKIES_ENABLED设置为True的时候scrapy就会把settings的cookie关掉,使用自定义cookie;

携带cookie模拟登录:

1、在settings中设置cookie 在请求头中添加UA 和 cookie即可

2、通过中间件设置cookie request.cookies = cookie_dict(需要将复制的cookie字符串转换成字典) 将 settings 文件中的 COOKIES_ENABLED 设置为True

3、重写 start_requests 方法 携带上 cookie 发送请求 构造请求 并且yield(需要cookie字符串转成字典)

Scrapy发送 post 请求:

参考 GitHub 案例