Scrapy

Scrapy

Yiuhang Chan

简介

Scrapy是一个用Python编写的高效且结构化的网页抓取框架,广泛应用于数据挖掘、网站监测和自动化测试。它的设计初衷是为了方便用户爬取网站数据和提取结构化数据,但它的用途不仅限于网页抓取,还包括处理API返回的数据(如Amazon Associates Web Services)或进行更广泛的网络爬取任务。

Scrapy的一个关键特性是它使用了Twisted异步网络库来处理网络通信。

信息

  1. 异步编程:
    • 在传统的同步编程模型中,任务按顺序一个接一个地执行。如果一个任务需要等待(例如,等待网络响应),程序将在此期间停止执行后续任务。
    • 异步编程允许程序在等待一个任务完成的同时继续执行其他任务。这种方式非常适合处理I/O密集型任务,比如网络请求,因为程序不需要在每个请求完成时都暂停执行。
  2. Twisted异步网络库:
    • Twisted是一个事件驱动的网络编程框架,专为Python设计。它支持多种协议,可以用于创建高性能的网络服务器和客户端。
    • 在Scrapy中,Twisted用于处理网络请求。当Scrapy发出一个请求并等待服务器响应时,它不会阻塞整个爬虫的进程。相反,Scrapy可以利用这段时间执行其他任务,如处理已经收到的数据或发出更多的网络请求。

Scrapy中的应用:

  • Scrapy利用Twisted的异步特性来提高爬虫的效率。它能够同时处理多个网络请求,而不会因为单个请求的延迟而阻塞整个爬虫。
  • 这使得Scrapy非常适合执行大规模的网络抓取任务,因为它可以有效地利用网络资源和处理能力,同时维持高效率的数据收集。

使用原因:

1.为了更利于我们将精力集中在请求与解析上
2.企业级的要求

1
2
3
4
5
flowchart TD
A(找到目标数据) --> B(分析请求流程)
B --> C(构造http请求)
C --> D(提取数据)
D --> E(数据持久化)

运行流程

Scrapy框架的体系结构

Scrapy框架的体系结构由几个重要组件组成,这些组件协同工作,实现了从发送请求到提取数据再到存储数据的整个流程:

  1. 引擎(Engine): Scrapy引擎是框架的核心,负责控制数据流在各组件之间的流动,触发事件。
  2. 调度器(Scheduler): 负责接收引擎发送的请求,并将它们加入队列中。在引擎请求新请求时,调度器发送下一个要抓取的请求给引擎。
  3. 下载器(Downloader): 负责从Internet上下载网页内容,并将其封装为响应对象,然后返回给引擎。
  4. 蜘蛛(Spiders): 是用户编写用来解析响应并从中提取数据(提取的项目)或额外的跟进URL的类。
  5. 项目管道(Item Pipeline): 负责处理由蜘蛛提取出来的项目,通常包括清洗、验证和存储等。
  6. 下载器中间件(Downloader Middlewares): 位于引擎和下载器之间,处理引擎和下载器之间的请求和响应。
  7. 蜘蛛中间件(Spider Middlewares): 位于引擎和蜘蛛之间,处理蜘蛛的输入(响应)和输出(项目和新的请求)。

信息

Downloader Middleware(下载中间件)

这些钩子允许处理、修改和自定义发往下载器的请求以及从下载器返回的响应。下载中间件在Scrapy的请求/响应处理流程中提供了几个关键的扩展点。例如,可以使用它来添加HTTP头部到请求中,或者处理从网站返回的HTTP响应。这些中间件的用途包括:

  • 在请求发送到下载器之前修改或过滤请求。
  • 在响应发送到爬虫之前处理或修改响应。
  • 直接生成新的请求,而不是处理当前的响应。
  • 选择性地放弃某些请求

Spider Middleware(爬虫中间件)

爬虫中间件则是处理传入响应、传出项目(item)和请求的钩子。这些中间件在Scrapy的数据处理流程中处于核心位置,允许用户对爬虫的输入和输出进行细致的控制。使用爬虫中间件,可以:

  • 在爬虫处理回调之后处理请求或项目。
  • 修改或过滤开始请求(由 start_requests 方法生成的请求)。
  • 处理由于响应内容引发的异常,根据响应内容调用错误回调(errback)。

数据流过程

  1. 启动过程:
    • Scrapy开始运行,初始化了调度器、下载器以及蜘蛛。
  2. 爬取过程:
    • 引擎向蜘蛛请求第一个要爬取的URL。
    • 蜘蛛返回第一个要爬取的请求给引擎。
    • 引擎将请求发送到调度器,并请求下一个URL。
    • 调度器返回下一个要爬取的请求给引擎。
  3. 下载过程:
    • 引擎从调度器中取出请求,通过下载器中间件发送到下载器。
    • 下载器处理请求,返回响应给引擎。
    • 引擎收到响应后,通过蜘蛛中间件发送给相应的蜘蛛处理。
  4. 解析和提取数据:
    • 蜘蛛处理响应,提取数据并生成新的请求,返回给引擎。
    • 引擎将提取的数据发送到项目管道,并将新的请求发送到调度器。
    • 项目管道处理提取出的数据。
  5. 循环处理:
    • 以上过程在整个爬取过程中不断重复,直到调度器中没有更多的请求。
  6. 关闭过程:
    • 当所有预定的URL都被爬取,且调度器中没有更多的请求时,Scrapy关闭,爬取过程结束。

简单使用

项目命令

  1. 创建项目:
    • 使用命令 scrapy startproject <project_name> [project_dir] 创建新项目。其中 <project_name> 是必填项,表示项目名称;[project_dir] 是可选项,表示项目目录。
    • 例如:scrapy startproject db 会创建一个名为 ‘db’ 的Scrapy项目。
  2. 创建第一个爬虫:
    • 首先,需要进入到项目目录中,使用 cd <project_name> 命令。
    • 然后,使用 scrapy genspider <name> <domain> 创建一个新的爬虫。这里 <name> 是爬虫的名称,而 <domain> 是爬虫将要爬取的域名。
    • 例如:scrapy genspider example example.com 会在项目的 spiders 目录下创建一个名为 ‘example’ 的爬虫,它针对的是 ‘example.com’ 网站。
  3. 运行项目:
    • 使用命令 scrapy crawl <spider_name> 来运行爬虫。这里 <spider_name> 是在创建爬虫时指定的名称。
    • 例如:scrapy crawl example 会运行名为 ‘example’ 的爬虫。
  4. 设置配置文件:
    • settings.py 文件中,可以配置各种参数,比如 ROBOTSTXT_OBEY(是否遵守网站的robots.txt规则)和 DEFAULT_REQUEST_HEADERS(默认的HTTP头部)。

警告

  1. 由于可以设置多个爬虫,且通过name进行管理,因此scrapy genspider <name> <domain>创建新爬虫的时候的时候name是不能重复的,会报错

  2. 无数据可以查看是否限制了域名

    dbtest1.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import scrapy


    class Test1Spider(scrapy.Spider):
    name = "test1" #爬虫名
    allowed_domains = ["movie.douban.com"] #限制域名,可以添加多个域名(列表)
    start_urls = ["https://movie.douban.com/top250"] #初始页面,第一个抓取的页面,放在产生请求的地方也就是在爬虫部件

    def parse(self, response): #response就是上面start_urls的响应
    pass

项目文件介绍

爬取豆瓣电影

项目目的和要求

目标是创建一个 Scrapy 爬虫来爬取豆瓣电影的前 250 个电影信息。这些信息包括:

  1. 电影名称
  2. 导演和演员信息
  3. 电影评分

需要将这些信息保存到本地,同时通过 Scrapy 的管道(Pipelines)机制进行持久化。

项目搭建

创建 Spider 类

首先需要创建一个名为 Test1Spider 的类,该类继承自 scrapy.Spider。在这个类中,定义以下属性和方法:

  • name: 爬虫的唯一标识名
  • start_urls: 包含开始爬取的 URL 列表
  • parse(self, response): 是爬虫的核心方法,用于处理响应并返回提取的数据或新的请求。

创建命令示例:

scrapy genspider dbtest1 movie.douban.com

这将在 spiders 目录下生成 dbtest1.py 文件。

解析响应(Parsing Response)

parse 方法中,使用 response 对象进行页面内容的解析。可以利用 xpathcss 选择器提取所需的数据。

  1. 首先分析该页面结构:

    假设要获取标题,导演和分数等信息,通过分析可以发现class="info"即可包含所有得信息

    因此修改response直接使用xpath语法

    dbtest1.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Dbtest1Spider(scrapy.Spider):
    name = "dbtest1"
    allowed_domains = ["movie.douban.com"]
    start_urls = ["https://movie.douban.com/top250"] #修改为开始爬取的页面

    # parse 里面我们只需要关注 怎么解析就行, 因为scrapy会自动帮我们去请求指定的网址
    def parse(self, response):
    # 这个地方可以直接使用response 就可以了,response对象里面就已经绑定了lxml的一些方法
    node_list = response.xpath('//div[@class="info"]')

调试前置

可以在同目录下添加调试文件db_debug.py,方便对爬虫文件进行断点调试

db_debug.py:

1
2
3
4
from scrapy.cmdline import execute


execute(['scrapy', 'crawl', 'dbtest1']) #执行命令

通过调试可以发现传输了25行数据

清洗数据

对拿到的info标签进行解析,利用循环进行即可

首先查看标题标签

然后通过其结构利用xpath锁定其位置

dbtest1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import scrapy


class Dbtest1Spider(scrapy.Spider):
name = "dbtest1"
allowed_domains = ["movie.douban.com"]
start_urls = ["https://movie.douban.com/top250"] #修改为开始爬取的页面

# parse 里面我们只需要关注 怎么解析就行, 因为scrapy会自动帮我们去请求指定的网址
def parse(self, response):
# 这个地方可以直接使用response 就可以了,response对象里面就已经绑定了lxml的一些方法
node_list = response.xpath('//div[@class="info"]')
if node_list: #万一没有数据那么就不执行避免报错
for node in node_list:
# 标题
move_title = node.xpath('./div/a/span/text()').get()
# 工作人员
employee = node.xpath('./div/p/text()').get()
# 评分
score = node.xpath('./span[@class="rating_num"]/text()').get()

信息

在XPath中,.///是两种不同的路径选择符,它们用于指定当前节点的相对路径。理解它们之间的差异对于正确地从HTML文档中提取数据是很重要的。

  1. ./ - 当前节点下的直接子节点:
    • ./用于选择当前节点的直接子节点。
    • 当使用./时,它会限制搜索范围到直接下级的元素。
    • 在例子中,./div意味着选择当前节点(在这里是每个div[@class="info"])的直接子div元素。
  2. // - 文档中任何位置的节点:
    • //用于选择文档中任何位置的节点,而不考虑它们在文档中的位置。
    • 使用//时,它会从整个文档中搜索符合条件的节点,而不仅限于当前节点的子节点。
    • 在例子中,如果使用//div,它将会查找整个文档中所有的div元素,而不仅仅是div[@class="info"]下的div元素。

使用了./div,这意味着想要选择每个div[@class="info"]元素的直接子div元素。这是一种更精确的选择方式,确保只选取特定父元素下的子元素,避免从整个文档中获取不相关的div

调试1

断点调试后发现其获取的是对象,这是因为在Scrapy框架中,使用.xpath()方法返回的是一个SelectorList对象,而不是直接返回数据。SelectorList是Scrapy中的一个特殊类型,用于表示一组通过XPath选择器找到的元素。

  1. SelectorList 对象:
    • 当调用 .xpath() 方法时,返回的是一个 SelectorList 对象,而不是实际的数据。
    • SelectorList 是一个包含多个 Selector 对象的列表。每个 Selector 对象代表一个通过XPath表达式匹配到的节点。
  2. 使用 .get() 方法提取数据:
    • 为了从 SelectorSelectorList 对象中提取数据,需要使用 .get() 方法(在早期版本的Scrapy中,这个方法叫做 .extract_first())。
    • .get() 方法从 SelectorList 中提取第一个匹配的元素的数据。如果没有找到匹配的元素,它将返回 None
    • 如果想要提取所有匹配的元素,可以使用 .getall() 方法(旧版本中对应 .extract())。
  3. 为什么不直接返回数据:
    • 这种设计允许在决定如何处理匹配到的元素之前先检查它们。例如,可以检查 SelectorList 是否为空,或者提取它包含的所有元素。
    • 它还提供了更大的灵活性,例如先对匹配到的元素进行进一步的选择或应用额外的XPath表达式。

因此,要从中提取文本数据,需要调用 .get() 方法。

调试2

随后继续调试可以发现工作人员部分,带有额外空白字符

而使用 .strip() 方法在处理从网页提取的文本数据时是非常常见且有用的,特别是当文本包含了不必要的空白字符(如空格、制表符、换行符等)时。.strip() 方法的作用是移除字符串两端的空白字符,使得提取的数据更加整洁和一致。

原因和原理:

  1. 移除多余空白:
    • 网页中的文本经常包含了在HTML代码中用于格式化的额外空白字符。这些字符在网页上看不出来,但在提取文本数据时会被包含进来。
    • .strip() 方法用于移除这些不需要的空白字符,如前后的空格和换行符,这对于清洁数据和后续处理非常有帮助。
  2. 格式化和一致性:
    • 使用 .strip() 可以确保数据的格式一致,无论原始HTML中的格式如何。
    • 它有助于减少后续处理数据时的麻烦,特别是在比较、存储或展示数据时。
  3. 使用方法:
    • 在Python中,.strip() 方法无需任何参数即可移除字符串两端的空白字符。
    • 如果想要移除其他特定字符,也可以传递一个字符串作为参数给 .strip(),它将移除字符串两端的任何包含在该参数中的字符。

这样,employee 将不再包含字符串开头和结尾的空格或换行符,使得数据更加整洁和便于处理。

调试3

最后对分数进行调试,发现没有返回数据,而这是因为搜索节点的范围和深度的缘故造成的

  • 当使用 ./span[@class="rating_num"]/text()
    • 这里的 ./ 将会导致XPath表达式只在当前处理节点的直接子节点中进行搜索。如果当前处理节点是 div 元素的父节点,那么 ./div[@class="star"] 将会选中直接子节点中的 class="star"div 元素。然后,/span[@class="rating_num"]/text() 将会从这个选中的 div 元素中选择 class="rating_num"span 元素,并提取其文本内容。
    • 然而,由于当前处理的节点是 div[@class="star"] 内部的一个节点,或者更深层次的一个节点,因此 ./ 就不能正确地选中所需的元素了,因为它不会搜索更深层次的节点。
    • 所以会返回None
  • 当使用 .//span[@class="rating_num"]/text()
    • .// 表示从当前节点开始查找,不限于直接子节点,而是包括所有后代节点。
    • div[@class="star"] 表示选择 class 属性为 “star” 的 div 元素。
    • span[@class="rating_num"] 表示选择 class 属性为 “rating_num” 的 span 元素。
    • /text() 表示获取选定 span 元素的文本内容。
    • 由于 span 元素嵌套在 class="star"div 元素内部,所以需要使用 .// 来确保能够选中正确的元素,无论它位于何种深度

在XPath中,./.// 的差异主要在于它们搜索节点的范围和深度。这个差别对于定位和提取网页中的数据是至关重要的。

  1. ./ - 直接子节点:
    • ./ 表示选择当前节点的直接子节点。
    • 当使用 ./ 时,它将只在当前节点的下一级(即直接子节点)中查找匹配的元素。
    • 例如,如果当前节点是一个div元素,./span将只会选择该div元素的直接子span元素。
  2. .// - 当前节点及其所有后代节点:
    • .// 表示在当前节点及其所有后代(子节点、孙节点等)中选择节点。
    • 当使用 .// 时,它会搜索整个子树,找到所有匹配的元素,无论它们位于当前节点的哪个层级。
    • 所以,如果当前节点是一个div元素,.//span将会选择该div及其所有子节点中的所有span元素,无论这些span元素嵌套得有多深。

在实际应用中,如果不确定当前处理的节点在HTML结构中的确切位置,使用 .// 会更加安全,因为它不受节点深度的限制。而 ./ 更适合在已知确切的层次结构和上下文中使用。

dbtest1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import scrapy

class Dbtest1Spider(scrapy.Spider):
name = "dbtest1"
allowed_domains = ["movie.douban.com"]
start_urls = ["https://movie.douban.com/top250"] #修改为开始爬取的页面

# parse 里面我们只需要关注 怎么解析就行, 因为scrapy会自动帮我们去请求指定的网址
def parse(self, response):
# 这个地方可以直接使用response 就可以了,response对象里面就已经绑定了lxml的一些方法
node_list = response.xpath('//div[@class="info"]')
if node_list: #万一没有数据那么就不执行避免报错
for node in node_list:
# 标题
move_title = node.xpath('./div/a/span/text()').get()
# 工作人员
employee = node.xpath('./div/p/text()').get().strip()
# 评分
score = node.xpath('.//span[@class="rating_num"]/text()').get()

定义 Item

  1. Scrapy使用Item类来定义数据结构。当从非结构化数据源(如网页)提取数据时,使用Item对象可以确保数据结构化和一致性。
  2. Item对象类似于Python字典,但提供了额外的结构化和错误检测功能。这在大型项目中尤其重要,因为它减少了字段名错误和数据不一致的风险。
  3. 在Scrapy项目中,通常需要在items.py文件中定义Item类,然后在爬虫代码中导入和使用这些类。

Scrapy 使用 Item 类来定义结构化数据字段,它类似于 Python 的字典但提供额外的保护和便利。在 items.py 文件中定义数据结构。

dbtest1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import scrapy
from ..items import DbItem


class Dbtest1Spider(scrapy.Spider):
name = "dbtest1"
allowed_domains = ["movie.douban.com"]
start_urls = ["https://movie.douban.com/top250"] # 修改为开始爬取的页面

# parse 里面我们只需要关注 怎么解析就行, 因为scrapy会自动帮我们去请求指定的网址
def parse(self, response):
# 这个地方可以直接使用response 就可以了,response对象里面就已经绑定了lxml的一些方法
node_list = response.xpath('//div[@class="info"]')
if node_list: # 万一没有数据那么就不执行避免报错
for node in node_list:
# 标题
move_title = node.xpath('./div/a/span/text()').get()
# 工作人员
employee = node.xpath('./div/p/text()').get().strip()
# 评分
score = node.xpath('.//span[@class="rating_num"]/text()').get()

# 实例化
item = DbItem()
item['move_title'] = move_title
item['employee'] = employee
item['score'] = score

yield item

只会存储item.py里定义的字段

items.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class DbItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
move_title = scrapy.Field()
employee = scrapy.Field()
score = scrapy.Field()

信息

在Python中,点(.)和双点(..)是相对导入的一部分,它们表示当前和父包的目录位置。

  • 单个点(.)表示当前模块的目录位置。
  • 双点(..)表示父目录的位置。

当在一个Python模块中看到 from ..items import DbItem 这样的语句时,这意味着:

  • from ..items 表示从当前模块的父包中的 items 模块导入。
  • import DbItem 是指导入 items 模块中定义的 DbItem 类。

这种导入方式通常用在一个包结构中,使得模块可以导入同一包内或父包中的其他模块。例如,在Scrapy项目中,可能有以下的目录结构:

1
2
3
4
5
6
7
8
9
10
11
12
myproject/
├── myproject/
│ ├── __init__.py
│ ├── items.py
│ ├── middlewares.py
│ ├── pipelines.py
│ ├── settings.py
│ └── spiders/
│ ├── __init__.py
│ ├── spider1.py
│ └── spider2.py
└── scrapy.cfg

如果在 spider1.pyspider2.py 中需要导入 items.py 中定义的 DbItem 类,需要使用相对导入。由于 spiders 是一个子包,需要使用 .. 来表示 items.py 所在的父包 myproject

使用相对导入的好处是,它不依赖于包的具体位置,这使得整个包更易于重构和移动。然而,相对导入也要小心使用,因为它们只能用在一个包的内部,不能用于顶级模块,否则会引发 ImportError

信息

在Scrapy框架中,yield关键字用于从一个函数返回数据项,但与return不同,yield实现的是一种称为生成器的概念,允许函数在保持其状态的情况下产生一个序列的值,这使得函数能够在每次产生一个值后暂停执行,并在下一次从它停止的地方继续执行。

在Scrapy中,yield通常在爬虫的parse方法中使用,用来产生以下几种可能的对象:

  1. Item对象: 当爬虫解析网页并提取数据时,它会创建一个Item对象,并填充数据。然后通过yield语句返回这个Item对象,Scrapy引擎会将这个Item传递给Item Pipeline进行进一步的处理,如清洗、验证和存储。
  2. Request对象: 如果页面需要跟进链接或翻页,爬虫会创建一个Request对象,并通过yield返回。这个Request对象包含了请求的URL和一个回调函数,Scrapy会排队发送这个请求,并在收到响应后将其传递给指定的回调函数。

使用yield的优势在于它的内存效率和执行效率。生成器不需要在处理大量数据时将它们全部存储在内存中,它们只在需要时产生一个数据项。对于大规模的爬虫任务,这意味着更低的内存消耗和更好的性能。

下面是使用yield在Scrapy爬虫中返回Item对象的示例代码:

1
2
3
4
5
6
7
8
def parse(self, response):
# 解析页面,提取数据
for some_data in response.css('some_selector'):
item = MyItem()
item['field1'] = some_data.css('field1_selector::text').get()
item['field2'] = some_data.css('field2_selector::text').get()
# ...
yield item # 循环返回Item对象

在这个例子中,每次循环都会提取数据,创建一个Item对象,并通过yield返回它。Scrapy引擎接收到这些Item后,会将它们发送到Item Pipeline。

追踪链接(Following Links)

  1. 为了实现多页爬取,需要在爬虫中实现链接追踪。这通常涉及从当前页面提取下一页的链接或根据一定的规则构建新的URL。
  2. 可以在爬虫中使用类变量来追踪当前页码,并在解析函数中构造下一页的URL。如果没有更多的页面可以爬取,爬虫将停止。

要爬取所有250部电影的信息,需要从初始页面提取到其他页面的链接,并对其进行跟踪。这可以通过构造新的 scrapy.Request 对象并从 parse 方法返回实现

定义和使用管道(Pipelines)

  1. Item管道是Scrapy中用于处理爬虫返回的Item对象的组件。它们可以执行多种操作,如清理HTML数据、验证数据完整性、检查重复项和进行数据持久化。
  2. 创建管道组件通常涉及编写一个Python类,并在该类中实现特定的方法来处理Item对象。
  3. 为了激活特定的管道组件,需要在Scrapy的settings.py文件中的ITEM_PIPELINES设置中添加并配置它们。

管道用于处理 Spider 从网页中提取的 Item。常见的用途包括清洗数据、去重、存储数据到文件或数据库中。

  • pipelines.py 中定义一个管道类,用于处理 Item 对象。
  • settings.py 中启用管道(ITEM_PIPELINES 设置)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter #Pipeline可以更加通用地处理不同的Item类或字典。
import json #导入Python标准库中的json模块,用于将Python字典转换为JSON字符串。


class DbPipeline: #定义了一个名为DbPipeline的新类,这是一个Scrapy Pipeline的实现。

def open_spider(self, spider): #open_spider是一个特殊的方法,它在Scrapy爬虫开始运行时被调用。
#在open_spider方法中,打开一个名为dbtest1result.txt的文件用于写入('w'模式)
#并设置编码为utf-8。这个文件句柄被保存在self.f属性中,以便在整个Pipeline中使用。
self.f = open('dbtest1result.txt', 'w', encoding='utf-8')

def process_item(self, item, spider): #是Pipeline中的核心方法,每个Item在Pipeline中被处理时都会调用这个方法。
#将Item对象转换为字典(如果它还不是字典),然后转换为一个JSON字符串。
#ensure_ascii=False是为了确保非ASCII字符(如中文)能被正确处理。
#每个JSON字符串后面都加上一个换行符,为了确保写入文件时每个Item占据一行。
json_str = json.dumps(dict(item), ensure_ascii=False) + '\n'
self.f.write(json_str) #将JSON字符串写入之前打开的文件。
print(item) #在控制台上打印Item,这对于调试和监控Pipeline的处理是有用的。
return item #返回Item对象,这样它就能被后续的Pipeline组件进一步处理。

def close_spider(self, spider): #是另一个特殊的方法,它在爬虫关闭时调用。
self.f.close() #关闭文件句柄。这是一个好习惯,因为它可以确保所有内容都被正确地写入磁盘,并释放了系统资源。

存储数据

  1. 可以编写管道类来实现特定的数据处理需求,例如将爬取的数据保存到文件中。
  2. 管道的执行顺序是根据在settings.py中分配给它们的整数值确定的。

修改 pipelines.py 文件以添加数据存储逻辑。例如,将数据写入到本地的 film.txt 文件中。

运行爬虫

进入项目根目录,运行以下命令以启动爬虫:

scrapy crawl db

注意

  1. Robots.txt 协议: 默认情况下,Scrapy 会遵守 Robots.txt 协议。如果需要爬取被此协议禁止的内容,需在 settings.py 中将 ROBOTSTXT_OBEY 设置为 False

  2. 表头设置(伪装): 有些网站会根据请求的 User-Agent 进行内容过滤。如果需要,可在 settings.py 中设置自定义的表头。

  3. 管道激活: 需要在 settings.py 中激活管道,以便将提取的数据传递到 pipelines.py300是一个整数值,定义了Pipeline的执行顺序,数字越小越早执行。

  4. Item 字段定义: 在 items.py 中定义字段,这些字段决定了可以从爬取的页面中提取哪些数据。

Scrapy shell

scrapy shell 是一个强大的交互式环境,它允许开发者在不运行整个爬虫的情况下测试和调试Scrapy的数据抓取代码。它可以加载网页,执行选择器,甚至可以运行爬虫的解析函数,是Scrapy开发中不可或缺的调试工具。

在项目目录下输入 scrapy shell https://movie.douban.com/top250 会启动Scrapy shell并预加载豆瓣电影Top 250的页面进行调试。下面是对Scrapy shell中提供的一些快捷方法和对象的详细说明:

快捷方法

  • shelp():
    • 显示Scrapy shell可用的快捷方法和支持的对象的帮助文档。
  • fetch(url[,redirect=True]):
    • 用于在Scrapy shell中获取指定网址的响应。如果redirectTrue,则会跟随重定向。
  • fetch(request):
    • 与上面的方法相似,但这里可以传递一个Scrapy的Request对象,而不是URL字符串。
  • view(response):
    • 在本地浏览器中打开当前响应的页面,便于直观地查看Scrapy获取的数据与实际浏览器中呈现的网页之间的差异。

Scrapy 对象

  • crawler:
    • 表示当前运行的爬虫的Crawler对象,包含了很多与爬虫运行相关的信息和接口。
  • spider:
    • 当前被加载的Spider对象。如果在shell中加载了一个特定的页面,Scrapy会尝试为这个域创建一个爬虫对象。
  • request:
    • 当前被执行的Request对象。在执行fetch()方法后,可以通过这个对象查看请求的具体信息。
  • response:
    • 最近一次执行fetch()方法后返回的Response对象。可以通过它来访问页面的内容,并使用选择器提取数据。
  • settings:
    • 当前Scrapy项目的设置。可以通过它来查看或修改Scrapy的配置信息。

在Scrapy shell中,也可以使用所有的Python标准库和Scrapy提供的选择器,例如response.xpath()response.css()来测试和调试数据提取的代码。

总结起来,scrapy shell提供了一个沙箱环境,让开发者可以实时测试XPath或CSS选择器,查看响应内容,甚至是测试item pipelines的代码,这样可以在不必运行整个Scrapy项目的情况下快速调试和修正代码中的问题。

示例

使用IPython 作为控制台的后端在终端(Terminal)中运行 Scrapy shell,Scrapy 会自动检测到 IPython 并使用它,相较于标准 Python 解释器这样操作更加简便。

在项目文件目录下成功启动Scrapy shell后输入settings['DEFAULT_REQUEST_HEADERS']即可查看settings文件中的DEFAULT_REQUEST_HEADERS的默认配置信息

同样的可以查看当前的爬虫和爬虫类的属性值

例如spider.name

或者利用fetch()进行URL的切换

但是个人觉得还是用IDE的调试方便,但是一般对于已经部署在服务器的项目还是使用Shell才行,毕竟不用修改代码

Scrapy Selector 类

选择器 Selector 是 Scrapy 的核心部分,用于提取 HTML/XML 文档中的数据。

  • Scrapy 和 lxml 的关系:

    • Scrapy 默认使用 lxml 作为其解析器,这意味着即使没有直接导入 lxml,Scrapy 仍然依赖于它来解析 HTML/XML。
    • lxml 提供了底层的解析功能,而 Scrapy 的选择器在此基础上提供了更易用的接口。
  • 选择器的主要方法:

    • xpath(): 接受 XPath 表达式,用于选择 HTML/XML 文档中的元素。
    • css(): 使用 CSS 选择器来选择元素。
    • 这些方法返回的是选择器列表(SelectorList 对象),可以进一步用于提取或操作数据。
  • 提取数据的方法:

    • extract(): 返回所有匹配的元素的数据。
    • extract_first(): 返回第一个匹配元素的数据,如果没有匹配元素则返回 None
    • get()getall(): 它们是 extract_first()extract() 的别名。
  • 嵌套选择器的使用:

    • 在某些情况下,可能需要多次调用 .xpath().css() 来精确定位数据。
    • 示例:response.css('img').xpath('@src') 会首先选择所有 <img> 标签,然后提取它们的 src 属性。
  • 使用正则表达式提取数据:

    • .re(): 使用正则表达式提取数据,返回匹配的字符串列表。
    • .re_first(): 返回第一个匹配的字符串。
  • 示例 HTML 字符串:

    • 提供的 HTML 字符串html_str是一个豆瓣电影页面的一部分,包含电影信息如标题、导演、评分等。
    • 使用 Scrapy 选择器,可以提取这些信息,如电影名称、评分等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
html_str="""
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title">肖申克的救赎</span>
<span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
<span class="other">&nbsp;/&nbsp;月黑高飞(港) / 刺激1995(台)</span>
</a>
<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
</p>
<div class="star">
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>1980500人评价</span>
</div>

<p class="quote">
<span class="inq">希望让人自由。</span>
</p>
</div>
</div>
</div>
"""
  • 选择器使用的完整示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from scrapy.selector import Selector

# 1.通过文本构造实例对象
select_txt = Selector(text=html_str)
# 这段代码通过 Selector 类创建一个选择器实例,接受一个 HTML 字符串作为输入。允许对该 HTML 文档执行 XPath 或 CSS 查询。

# 提取电影的名字(提取单个内容使用get())
print(select_txt.xpath('//div[@class="info"]/div/a/span/text()').get())
# 这里使用 XPath 查询提取了电影名称。get() 方法返回第一个匹配元素的文本,适用于提取单个内容。

# 提取多个标签的内容(提取多个内容使用getall())
print(select_txt.xpath('//div[@class="info"]/div/a/span/text()').getall())
# 与上面类似,但使用 getall() 方法,它返回所有匹配元素的文本列表,适用于提取多个内容。

# 提取电影的名字(提取单个内容使用extract_first())
print(select_txt.xpath('//div[@class="info"]/div/a/span/text()').extract_first())
# 这行代码等效于使用 get() 方法。extract_first() 也返回第一个匹配元素的文本

# 提取多个标签的内容(提取多个内容使用extract())
print(select_txt.xpath('//div[@class="info"]/div/a/span/text()').extract())
# 这行代码等效于使用 getall() 方法。extract() 返回所有匹配元素的文本列表。

# 细分一下标签
print(select_txt.xpath('//div[@class="info"]/div/a/span[1]/text()').getall()[0])
# 这里通过细分 XPath 查询,只选择第一个 <span> 元素的文本,并通过索引 [0] 获取列表中的第一个元素。

# 2. 通过response 构造实例对象
from scrapy.http import HtmlResponse
response = HtmlResponse(url='http://www.example.com/', body=html_str.encode())
select_tet = Selector(response=response)
# 这里通过 HtmlResponse 对象创建了一个选择器实例。HtmlResponse 接受一个 URL 和 HTML 内容作为输入。

print(select_tet.xpath('//div[@class="info"]/div/a/span/text()').get())
print(response.selector.xpath('//div[@class="info"]/div/a/span/text()').get())
# 这两行代码都用于提取电影名称。第一行直接使用通过 HtmlResponse 创建的选择器,第二行使用 response 对象的 selector 属性。

print(select_tet.css('a').xpath('./span/text()').re_first('.*的'))
# 这里首先使用 CSS 选择器选择所有的 <a> 标签,然后使用 XPath 选择器进一步选择这些 <a> 标签中的 <span> 标签内的文本。最后,使用正则表达式方法 re_first() 提取包含“的”的第一个文本。

Selector 类的基础用法

1
2
from scrapy.selector import Selector
from scrapy.http import HtmlResponse

导入 Selector 类和 HtmlResponse 类的语句。

  1. Selector
    • Selector 类用于从 HTML 或 XML 文档中提取数据。
    • 它提供了使用 XPath 和 CSS 选择器查询文档的功能。
    • 通过 Selector 类,可以轻松地提取网页中的特定部分,如文本、链接、标签属性等。
  2. HtmlResponse
    • HtmlResponse 类是 Scrapy 用于表示 HTTP 响应的一个类。
    • 它是 Response 类的一个子类,专门用于处理 HTML 类型的内容。
    • 通过 HtmlResponse,可以访问响应的 URL、状态码、头部(headers)、正文(body)等信息。
    • 它通常与 Request 对象一起使用,表示发送请求后收到的 HTTP 响应。

这两个类在编写 Scrapy 爬虫时非常有用。例如,当发送一个 HTTP 请求并收到响应时,可以使用 HtmlResponse 对象来处理这个响应,并使用 Selector 对象来解析和提取所需的数据。

1. 通过文本构造 Selector 实例

1
select_txt = Selector(text=html_str)
  • Selector 是 Scrapy 框架中的一个核心类,用于执行对 HTML 或 XML 文档的选择和提取操作。
  • 这行代码创建了一个 Selector 的实例,命名为 select_txt
  • text=html_str:这里 text 参数用于传递 HTML 内容。
    • html_str 应该是一个包含 HTML 文档内容的字符串。在实际应用中,这个字符串通常是从网页请求中获取的 HTML 响应体。
    • 通过传递这个 HTML 字符串,Selector 类的实例 select_txt 将能够解析这个 HTML 文档。
  • 创建了 select_txt 实例后,可以使用它来进行数据提取。
  • Selector 提供了多种方法来查询和提取 HTML 文档中的数据,最常见的包括 XPath 和 CSS 查询。
    • 例如,select_txt.xpath('//div') 会选择 HTML 文档中所有的 <div> 元素。
    • 另一个例子,select_txt.css('div.classname') 会选择所有 class 属性为 classname<div> 元素。
  • 这行代码的主要功能是创建一个 Selector 对象,用于解析和操作给定的 HTML 字符串。这是 Scrapy 爬虫开发中常见的模式,用于从网页中提取信息,如提取链接、文本内容、图像地址等。通过这种方式,Scrapy 能够高效地处理和解析网页内容。
提取电影名称
1
select_txt.xpath('//div[@class="info"]/div/a/span/text()').get()
  • select_txt 是一个 Selector 对象,它包含了 HTML 文档的内容。

  • .xpath('//div[@class="info"]/div/a/span/text()') 是一个 XPath 查询。这个查询的含义如下:

    • //div[@class="info"]:这部分选择所有具有 class="info" 属性的 <div> 元素。// 表示在整个文档中查找,而 [@class="info"] 是一个条件,用于筛选具有指定类名的 <div> 元素。
    • /div/a/span:对于每个符合上述条件的 <div> 元素,进一步选择其内部的子 <div>,然后选择这些 <div> 内部的 <a> 元素,再选择这些 <a> 元素内部的 <span> 元素。这是一个层层深入的选择过程。
    • /text():最后,这部分选择了前面找到的 <span> 元素中的文本内容。text() 函数用于获取一个元素的文本部分。
  • .get():这个方法用于从上面的 XPath 查询中提取第一个匹配元素的文本内容。如果查询没有找到匹配的元素,则返回 None

提取所有匹配元素的文本列表
1
select_txt.xpath('//div[@class="info"]/div/a/span/text()').getall()
  • 类似于前面的查询,但使用 getall() 方法来获取所有匹配元素的文本内容列表。
  • .getall():这个方法从 XPath 查询中提取所有匹配元素的文本内容,并返回一个列表。如果没有找到匹配的元素,则返回一个空列表。
使用 extract_first() 提取第一个匹配元素的文本
1
select_txt.xpath('//div[@class="info"]/div/a/span/text()').extract_first()
  • extract_first() 方法的功能与 get() 相似,用于提取第一个匹配元素的文本。
使用 extract() 提取所有匹配元素的文本列表
1
select_txt.xpath('//div[@class="info"]/div/a/span/text()').extract()
  • extract() 方法的功能与 getall() 相似,用于提取所有匹配元素的文本内容列表。
提取特定子元素的文本
1
select_txt.xpath('//div[@class="info"]/div/a/span[1]/text()').getall()[0]
  • select_txt 应该是一个 Selector 对象,包含了 HTML 文档的内容。

  • .xpath('//div[@class="info"]/div/a/span[1]/text()') 是一个 XPath 查询。

    • //div[@class="info"]:选择所有具有 class="info" 属性的 <div> 元素。// 表示在整个文档中查找。
    • /div/a/span[1]:对于每个符合上述条件的 <div> 元素,进一步选择其内部的子 <div>,然后选择这些 <div> 内部的 <a> 元素,再选择这些 <a> 元素内部的第一个 <span> 元素(由 [1] 指定)。
    • /text():选择前面找到的 <span> 元素中的文本内容。
  • .getall():这个方法用于从 XPath 查询中提取所有匹配元素的文本内容,并返回一个列表。

  • [0]:这个索引用于从列表中获取第一个元素。在 Python 中,列表的索引从 0 开始。

2. 通过 response 构造 Selector 实例

1
2
3
4
# 创建 HtmlResponse 对象
response = HtmlResponse(url='http://www.example.com/', body=html_str.encode())
# 创建 Selector 对象
select_tet = Selector(response=response)
  • HtmlResponse 是 Scrapy 框架中用来表示 HTTP 响应的一个类。

  • url='http://www.example.com/':这里指定了响应所对应的 URL。在实际的 Scrapy 应用中,这通常是发起请求的目标 URL。

  • body=html_str.encode():body

    参数用于提供 HTTP 响应的正文内容。

    • html_str 应该是一个包含 HTML 内容的字符串。
    • .encode() 方法将这个字符串转换为字节序列。在 HTTP 通信中,正文内容通常以字节形式存在,所以需要进行这样的转换。
  • Selector 类在 Scrapy 中用于选择和提取 HTML 或 XML 文档中的数据。

  • response=response:这里将前面创建的 HtmlResponse 对象传递给 Selector。这样,Selector 就可以使用 HtmlResponse 中的 HTML 内容进行解析和数据提取。

  • select_tet 是创建的 Selector 对象的实例,它现在包含了 response 中的 HTML 数据,并可以使用该对象进行各种选择器查询(如 XPath 或 CSS 查询)。

  • 这两行代码组合使用 HtmlResponseSelector 类来处理和解析 HTML 数据。首先,使用 HtmlResponse 来模拟一个 HTTP 响应,包括它的 URL 和正文内容。然后,创建一个 Selector 对象来解析这个响应的 HTML 内容,并提供数据提取的功能。这是在 Scrapy 爬虫中常见的模式,用于从网页中提取所需的信息。

使用 Selector 实例和 response 的 selector 属性提取数据
1
2
select_tet.xpath('//div[@class="info"]/div/a/span/text()').get()
response.selector.xpath('//div[@class="info"]/div/a/span/text()').get()
  • select_tet 应该是一个由 Selector 类实例化的对象。这个对象包含了某个 HTML 文档的内容。

  • .xpath('//div[@class="info"]/div/a/span/text()')

    是一个 XPath 选择器表达式。这个表达式的作用是:

    • //div[@class="info"]:选择所有具有 class="info" 属性的 <div> 标签。
    • /div/a/span:在每个这样的 <div> 标签内,进一步选择嵌套的 <div>,然后选择其内部的 <a> 标签,再选择 <a> 标签内的 <span> 标签。
    • /text():提取这些 <span> 标签内的文本内容。
  • .get():这个方法用于获取匹配的第一个元素的文本内容。如果没有找到匹配的元素,将返回 None

  • response 应该是一个 HtmlResponse 对象,通常在 Scrapy 中表示对某个 URL 请求的响应。

  • response.selectorHtmlResponse 对象的属性,它提供了一个 Selector 对象,用于在响应的 HTML 内容上进行选择器查询。

  • .xpath('//div[@class="info"]/div/a/span/text()') 和第一行代码中的 XPath 表达式相同,作用也相同,用于选择特定结构的元素并提取其文本内容。

  • .get() 的作用同上,用于获取匹配的第一个元素的文本内容。

  • 这两行代码实现的功能是相同的:从 HTML 文档中提取具有特定结构的元素(在这种情况下是某些 <span> 标签内的文本内容)。区别在于它们的选择器来源不同 — 第一行代码使用的是直接从 HTML 文本创建的选择器,而第二行代码使用的是从 HtmlResponse 对象创建的选择器。这种灵活性使得 Scrapy 可以适应不同的数据提取场景。

结合 CSS 和 XPath 选择器及正则表达式提取数据
1
select_tet.css('a').xpath('./span/text()').re_first('.*的')
  • select_tet.css('a'):
    • select_tet 应该是一个 Selector 对象,它包含了从网页中提取的 HTML 数据。
    • .css('a') 是一个 CSS 选择器,用来选取所有的 <a> 标签。在 Scrapy 中,css 方法用于执行 CSS 选择器查询。
    • 这一步的结果是一个新的 SelectorList 对象,包含了 HTML 文档中所有的 <a> 标签。
  • .xpath('./span/text()'):
    • .xpath() 是一个 XPath 选择器方法,用来进一步从前一步的结果中筛选数据。
    • './span/text()' 是一个 XPath 查询,它的含义是:在当前节点(这里指的是每个 <a> 标签)的基础上,选择其子节点中的 <span> 标签,并获取这些 <span> 标签的文本内容。
    • 这一步的结果是一个 SelectorList 对象,包含了所有选中 <span> 标签的文本内容。
  • .re_first('.\*的'):
    • .re_first() 是一个正则表达式方法,用于在之前选择的文本中进行模式匹配。
    • '.*的' 是一个正则表达式,. 表示任意字符,* 表示零次或多次重复, 是要匹配的具体字符。因此,这个表达式的意思是匹配任何以 “的” 结尾的字符串。
    • re_first 表示只提取第一个匹配的结果。如果没有找到匹配项,则返回 None

在 HTML 文档中查找所有 <a> 标签,然后在每个 <a> 标签中查找 <span> 标签的文本内容,并从这些文本中提取第一个以 “的” 结尾的字符串。这种方法在处理 HTML 数据时非常灵活,可以有效地结合不同的选择器和正则表达式来提取复杂的数据结构。

Scrapy Spider 类

  • Spider的名称 (name):

    • 这是一个字符串,用于定义此蜘蛛的名称。
    • 名称必须唯一,因为它是Scrapy定位和实例化蜘蛛的方式。
    • 这是最重要的蜘蛛属性,必须提供。
  • 起始URLs (start_urls):

    • 这是蜘蛛开始爬取的URL列表。
    • 爬虫的第一页下载将是此列表中的页面。
    • 后续的请求将从这些起始URL中提取的数据中连续生成。
  • 自定义设置 (custom_settings):

    • 这些设置在运行特定蜘蛛时会覆盖项目范围的全局设置。
    • 必须定义为类属性,因为在实例化蜘蛛之前,设置就已经更新。

Spider 类的基础结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Spider(object_ref):
"""Base class for scrapy spiders. All spiders must inherit from this
class."""

name = None
custom_settings = None

def __init__(self, name=None, **kwargs):
if name is not None:
self.name = name
elif not getattr(self, 'name', None):
raise ValueError("%s must have a name" % type(self).__name__)
self.__dict__.update(kwargs)
if not hasattr(self, 'start_urls'):
self.start_urls = []
  • class Spider(object_ref): 这行定义了一个名为 Spider 的类。它从 object_ref 继承,但这里的 object_ref 看起来像是一个错误或者不完整的代码,因为通常 Python 中的类是从 object 类继承的。这可能是一个笔误或者特定项目中的自定义实现。
  • 类的文档字符串说明了这个类是所有 Scrapy Spider 的基类,意味着所有的 Scrapy 爬虫都应该从这个类继承。
  • name = None: 这是一个类级别的属性,用于存储爬虫的名称。在 Scrapy 中,每个爬虫的名称应该是唯一的。
  • custom_settings = None: 这也是一个类级别的属性,用于定义特定于此爬虫的自定义设置,这些设置将覆盖全局设置。
  • def __init__(self, name=None, **kwargs): 这是 Spider 类的构造函数。它接受一个可选的 name 参数和任意数量的关键字参数(**kwargs)。
  • 这部分代码用于设置爬虫的名称。如果构造函数中提供了 name,它将被用作爬虫的名称。
  • 如果没有提供 name 并且类的 name 属性也没有被设置,会抛出一个 ValueError 异常,因为每个爬虫必须有一个唯一的名称。
  • self.__dict__.update(kwargs): 这行代码将所有通过 **kwargs 传入的关键字参数添加到类的实例字典中。这允许在创建爬虫实例时传入额外的属性或设置。
  • if not hasattr(self, 'start_urls'): 这里检查实例是否有 start_urls 属性,如果没有,则初始化为空列表。start_urls 是爬虫开始爬取的 URL 列表。
  • 这个类是 Scrapy 爬虫的一个基本框架,提供了命名、自定义设置和初始化的基本机制。任何具体的 Scrapy 爬虫都应该继承这个类,并根据需要提供具体的实现细节。

日志系统 Logger

在 Scrapy 的 Spider 类中,日志系统被用于记录信息、警告、错误等。这对于监控爬虫的行为、调试和记录重要事件非常有用。

Logger 属性

1
2
3
4
@property
def logger(self):
logger = logging.getLogger(self.name)
return logging.LoggerAdapter(logger, {'spider': self})
  • @property:这是一个 Python 装饰器,用于将一个方法转换为属性。这意味着可以通过 self.logger 访问这个方法返回的值,而不是 self.logger()
  • logger = logging.getLogger(self.name):这里创建了一个日志记录器(logger)。getLogger(self.name) 使用爬虫的名称(self.name)获取一个日志记录器实例。如果不存在具有该名称的记录器,将自动创建一个。
  • return logging.LoggerAdapter(logger, {'spider': self}):返回一个 LoggerAdapter 实例。这是一个对标准日志记录器的包装,它提供了额外的上下文信息,使得每个日志消息都能够知道是由哪个爬虫实例产生的。这里的上下文信息是 {'spider': self},即当前的爬虫实例。

Log 方法

1
2
3
4
5
6
7
8
def log(self, message, level=logging.DEBUG, **kw):
"""Log the given message at the given log level

This helper wraps a log call to the logger within the spider, but you
can use it directly (e.g. Spider.logger.info('msg')) or use any other
Python logger too.
"""
self.logger.log(level, message, **kw)
  • def log(self, message, level=logging.DEBUG, **kw): 这是一个辅助方法,用于在爬虫内部发送日志消息。
  • level=logging.DEBUG:默认的日志级别设置为 DEBUG。日志级别决定了记录的消息类型。常见的级别包括 DEBUG, INFO, WARNING, ERROR, 和 CRITICAL。
  • self.logger.log(level, message, **kw):这个调用使用了之前定义的 logger 属性。它将消息 message 记录到日志中,级别为 level**kw 可以传递额外的关键字参数。

使用示例

1
2
3
4
5
6
7
class MySpider(scrapy.Spider):
name = 'my_spider'

def parse(self, response):
# 使用日志记录信息
self.logger.info('Parsing response from %s', response.url)
# 其他解析逻辑...

在这个例子中,每当 parse 方法被调用时,都会记录一个包含响应 URL 的信息级别日志。

from_crawler 和 _set_crawler 方法

Spider 类的 from_crawler 类方法和 _set_crawler 实例方法。这些方法在 Scrapy 的内部机制中扮演重要角色,尤其是在爬虫的初始化过程中。

from_crawler 方法

1
2
3
4
5
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
spider = cls(*args, **kwargs)
spider._set_crawler(crawler)
return spider
  • @classmethod:这是一个 Python 装饰器,用于定义一个类方法。不同于普通的实例方法,类方法接收类本身作为第一个参数(通常命名为 cls)而非类的实例。
  • from_crawler 是 Scrapy 用来创建 Spider 实例的标准方法。它接收一个 crawler 对象作为参数,以及任意数量的额外的位置和关键字参数。
  • spider = cls(*args, **kwargs):这行代码使用传入的参数创建了一个 Spider 实例。
  • spider._set_crawler(crawler):这行代码调用 _set_crawler 方法,将 crawler 对象设置到创建的 Spider 实例上。
  • return spider:返回创建好的 Spider 实例。

_set_crawler 方法

1
2
3
4
def _set_crawler(self, crawler):
self.crawler = crawler
self.settings = crawler.settings
crawler.signals.connect(self.close, signals.spider_closed)
  • 这是一个内部方法,用于在 Spider 实例上设置 crawler 对象。
  • self.crawler = crawler:将传入的 crawler 对象赋值给 Spider 实例的 crawler 属性。
  • self.settings = crawler.settings:将 crawler 对象的设置赋值给 Spider 实例的 settings 属性。这样,Spider 可以访问和使用 Scrapy 项目的设置。
  • crawler.signals.connect(self.close, signals.spider_closed):这行代码连接了 Spider 的 close 方法到 Scrapy 的 spider_closed 信号。当爬虫关闭时,这个信号会被触发,并调用 Spider 的 close 方法。

在 Scrapy 框架中,from_crawler 方法通常由框架自身调用,用于创建并初始化 Spider 对象。这个过程包括设置爬虫的配置和信号处理等。一般情况下,开发者无需覆盖这个方法,除非需要进行一些特殊的初始化操作。from_crawler_set_crawler 方法是 Scrapy 框架中 Spider 类的重要组成部分,它们负责爬虫的初始化和设置。这些方法确保了每个 Spider 实例可以访问到它所需的资源和配置,同时也使得爬虫能够正确响应 Scrapy 框架的信号。

start_requests 方法

start_requests 是一个关键方法,用于生成爬虫启动时的初始请求。这个方法只会在爬虫开始时调用一次。下面是对这个方法的代码和概念的详细解释和整理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def start_requests(self):
cls = self.__class__
if not self.start_urls and hasattr(self, 'start_url'):
raise AttributeError(
"Crawling could not start: 'start_urls' not found "
"or empty (but found 'start_url' attribute instead, "
"did you miss an 's'?)")
if method_is_overridden(cls, Spider, 'make_requests_from_url'):
warnings.warn(
"Spider.make_requests_from_url method is deprecated; it "
"won't be called in future Scrapy releases. Please "
"override Spider.start_requests method instead (see %s.%s)." % (
cls.__module__, cls.__name__
),
)
for url in self.start_urls:
yield self.make_requests_from_url(url)
else:
for url in self.start_urls:
yield Request(url, dont_filter=True)
  • def start_requests(self): 这是 Scrapy Spider 类中的一个实例方法,用于生成初始的网页请求。
  • cls = self.__class__: 获取当前实例的类。
  • if not self.start_urls and hasattr(self, 'start_url'): 这个条件检查是否定义了 start_urls 属性。如果 start_urls 没有定义或为空,并且错误地定义了 start_url(缺少末尾的 ‘s’),则抛出异常。
  • if method_is_overridden(cls, Spider, 'make_requests_from_url'): 检查是否重写了 make_requests_from_url 方法。这个方法已被弃用,Scrapy 鼓励使用 start_requests 方法。
  • warnings.warn(...): 如果使用了弃用的方法,显示警告信息。
  • for url in self.start_urls: 遍历 start_urls 列表中的每个 URL。
  • yield self.make_requests_from_url(url): 对于每个 URL,使用 make_requests_from_url 方法生成请求。这是兼容旧代码的方式。
  • yield Request(url, dont_filter=True): 对于每个 URL,创建一个 Scrapy 的 Request 对象并返回。dont_filter=True 表示对这些请求不应用去重过滤器。
  • 通常,Scrapy 爬虫定义了 start_urls 列表,并依赖 start_requests 方法来为这些 URL 创建初始请求。可以覆盖 start_requests 方法以提供更复杂的启动逻辑,例如从外部数据源读取 URL,或添加特殊的请求头和元数据。
  • start_requests 方法是 Scrapy 爬虫的起点,负责生成爬虫的初始请求。这个方法应返回一个可迭代对象,其中包含了爬虫开始时需要处理的 Request 对象。通过重写这个方法,开发者可以自定义爬虫的启动行为,例如,从外部文件读取起始 URL,或者添加特定的请求参数和头部

parse 方法

这是Scrapy在其请求未指定回调时处理下载的响应时使用的默认回调

1
2
def parse(self, response):
raise NotImplementedError('{}.parse callback is not defined'.format(self.__class__.__name__))
  • parse 是 Scrapy 爬虫中最重要的方法之一。它是默认的回调函数,用于处理由 Scrapy 发出的请求所返回的响应。
  • 当一个请求没有指定特定的回调函数时,Scrapy 会自动调用 parse 方法。
  • 在这个示例中,parse 方法被定义为抛出 NotImplementedError 异常。这是一个通常的做法,用来提示开发者应该在自己的爬虫类中覆盖这个方法,提供具体的实现逻辑。
  • 实际使用中,parse 方法通常包含解析响应(response 对象)并提取数据或进一步生成请求(Request 对象)的逻辑。
  • parse 是处理响应的默认方法

close 方法

  • close 方法在爬虫关闭时被调用。它是一个钩子(hook),提供了一种机会在爬虫结束时执行某些操作,比如清理资源、保存状态、发出通知等。
  • close 方法的具体实现会根据爬虫的需求而有所不同。在 Scrapy 框架的默认实现中,这个方法可能并未显式地定义,但可以根据需要在自定义爬虫中覆盖它。
  • 在开发 Scrapy 爬虫时,通常需要实现 parse 方法来定义如何处理响应。这可能包括解析 HTML、处理数据和生成新的请求。
  • close 方法则用于定义在爬虫关闭时需要执行的任何清理或结束任务。
  • close 提供了在爬虫结束时执行操作的机会。

次级页面抓取及数据传递拼接

次级页面抓取

在 Scrapy 爬虫中,处理多级页面通常涉及以下步骤:

  • 一级页面(列表页)

    • 在列表页,会解析出指向详情页的链接,并对每个链接发起请求以进入二级页面。
  • 二级页面(详情页)

    • 在详情页,会提取所需的具体数据。

这个过程通常在 parse 方法(处理列表页)和一个自定义的回调方法(处理详情页)中实现。

详情页抓取

1
2
def get_detail(self, response):
pass
  • get_detail 是一个自定义的回调方法,用于处理从列表页提取的详情页链接的响应。
  • 这个方法中,编写解析详情页的逻辑,例如提取特定的数据。

数据提取示例

  • 第一种方式:'//div[@id="link-report"]/span[@property="v:summary"]'。这个 XPath 查询用于提取比较简单的结构的数据。
  • 第二种方式:'//div[@id="link-report"]/span[@class="all hidden"]/text()|//div[@id="link-report"]/span[@property="v:summary"]/text()'。这个查询适用于当数据结构更复杂或有多种可能的格式时。它使用了 XPath 的联合操作符 | 来匹配多个可能的路径。

参数的传递拼接

  • 在 Scrapy 中,meta 参数用于在不同的请求之间传递数据。
  • 当从列表页发起对详情页的请求时,可以使用 meta 参数携带需要在详情页中使用的数据。例如,可能想传递从列表页提取的某些上下文信息到详情页。

使用 meta 的示例

1
2
3
4
5
def parse(self, response):
# 提取详情页链接
detail_url = response.xpath('some_xpath_to_detail_url').get()
# 向详情页发起请求,并传递额外的数据
yield scrapy.Request(detail_url, callback=self.get_detail, meta={'item': 'some_data'})

在这个示例中,parse 方法提取了详情页的链接,并发起了一个新的请求。通过设置 meta={'item': 'some_data'},任何想从列表页到详情页传递的数据都可以包含在这个 meta 字典中。

在 Scrapy 爬虫中处理多级页面时,通常会在列表页解析出详情页的链接,并为这些链接发起请求。在处理这些请求的回调方法中,会提取详情页的具体数据。使用 meta 参数可以在不同请求之间传递数据,这对于保持爬虫逻辑的连贯性和传递上下文信息非常重要。

豆瓣为例子

Spider 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import scrapy
import json
from ..items import DbItem

class Db250Spider(scrapy.Spider):
name = 'db250'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250']
page_num = 0

def parse(self, response):
node_list = response.xpath('//div[@class="info"]')
if node_list:
for node in node_list:
# 标题
movie_name = node.xpath('./div/a/span/text()').get()
# 导演
director = node.xpath('./div/p/text()').get().strip()
# 分数
score = node.xpath('.//span[@class="rating_num"]/text()').get()

item = DbItem()
item["movie_name"] = movie_name
item["director"] = director
item["score"] = score

# 电影详情页
detail_url = node.xpath('./div/a/@href').get()
yield scrapy.Request(detail_url, callback=self.get_detail, meta={"info":item})

self.page_num +=1
page_url = 'https://movie.douban.com/top250?start={}&filter='.format(self.page_num*25)
yield scrapy.Request(page_url, callback=self.parse)
else:
return

# 详情页解析
def get_detail(self,response):
item = DbItem()
info = response.meta.get("info")
item.update(info)
description_list = response.xpath('//div[@id="link-report"]/span[@class="all hidden"]/text()|//div[@id="link-report"]/span[@property="v:summary"]/text()').getall()
description = ''.join([des.strip() for des in description_list])

item["description"] = description
yield item

这段代码是一个 Scrapy 爬虫的示例,用于爬取豆瓣电影 Top 250 的相关信息。下面是对这个代码的详细分析:

爬虫定义
1
2
3
4
5
class Db250Spider(scrapy.Spider):
name = 'db250'
allowed_domains = ['movie.douban.com']
start_urls = ['https://movie.douban.com/top250']
page_num = 0
  • 这个类继承自scrapy.Spider,是Scrapy框架中用于创建爬虫的基础类。
  • class Db250Spider(scrapy.Spider): 这行定义了一个名为Db250Spider的新类,它继承自scrapy.Spider。这意味着Db250Spider是一个Scrapy爬虫,可以继承并使用Scrapy框架提供的所有功能和属性。
  • name = 'db250' 这里设置了爬虫的名字为db250。这个名称是Scrapy项目中唯一的标识符,用于在命令行中指定和运行特定的爬虫。
  • allowed_domains = ['movie.douban.com'] allowed_domains是一个列表,包含了爬虫允许爬取的域名。这有助于确保爬虫不会跨域爬取。在这个例子中,爬虫只会爬取movie.douban.com域下的页面。
  • start_urls = ['https://movie.douban.com/top250'] start_urls是一个包含起始URL的列表。当爬虫启动时,Scrapy会自动开始从这些URL发起请求。在这个例子中,爬虫将从豆瓣电影Top 250的主页开始爬取。
  • page_num = 0 page_num是一个类属性,用于跟踪当前爬取的页码。这在处理分页或需要在多个页面之间导航时非常有用。在这个例子中,它被初始化为0。
  • 接下来在这个爬虫类中定义如parse等方法,以指定如何解析和处理每个请求的响应,以及如何从中提取数据或者进一步生成新的请求。例如,解析豆瓣电影Top 250页面的响应,提取电影信息,然后爬取每个电影的详细页面等。
主页解析方法 parse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def parse(self, response):
node_list = response.xpath('//div[@class="info"]')
if node_list:
for node in node_list:
movie_name = node.xpath('./div/a/span/text()').get()
director = node.xpath('./div/p/text()').get().strip()
score = node.xpath('.//span[@class="rating_num"]/text()').get()

item = DbItem()
item["movie_name"] = movie_name
item["director"] = director
item["score"] = score

detail_url = node.xpath('./div/a/@href').get()
yield scrapy.Request(detail_url, callback=self.get_detail, meta={"info":item})

self.page_num += 1
page_url = 'https://movie.douban.com/top250?start={}&filter='.format(self.page_num*25)
yield scrapy.Request(page_url, callback=self.parse)
else:
return
0. 方法定义
1
def parse(self, response):
  • self 是对当前对象实例(即Spider对象)的引用。
  • response 是一个包含了HTTP响应数据的对象。它包括响应内容(通常是HTML)、URL、HTTP头等信息。
  • parse方法的主要功能是处理response,提取有用信息,或者进一步生成需要跟进的URL请求。
  • 在这个方法中,通常会使用XPath或CSS选择器来解析响应内容(如提取数据)。
  • 数据提取:可以使用response.xpath()response.css()方法来选择HTML元素,然后提取所需数据。
  • 生成跟进请求:如果页面中有链接到其他页面,且想爬取那些页面的数据,可以通过scrapy.Request生成新的请求。
  • parse方法可以返回以下类型的对象:
    • 字典(在Python中通常是通过yield语句生成)。
    • scrapy.Item对象,这是一个更结构化的方式来管理数据。
    • Request对象,用于生成新的爬取请求。
    • 或者它们的组合。
  • parse方法是Scrapy爬虫的核心,用于处理响应并从中提取信息,或者根据页面中的链接生成新的请求。
1. 解析节点列表
1
node_list = response.xpath('//div[@class="info"]')
  • 这行代码使用 XPath 查询从响应中提取所有类属性为 "info"<div> 元素。
  • 这些 <div> 元素包含了电影的主要信息,并存储在 node_list 中。
  • response:
    • 这通常是一个由Scrapy框架(或类似的库)提供的响应对象。
    • 它代表了一个从网页请求中获得的HTTP响应。
    • 这个对象包含了完整的网页数据,通常是HTML格式。
  • .xpath():
    • 这是一个方法,用于对response对象中的HTML内容执行XPath查询。
    • XPath是一种在XML和HTML文档中查找信息的语言。
    • 通过XPath,可以导航文档的结构,并选择需要的元素、属性等。
  • '//div[@class="info"]':
    • 这是传递给.xpath()方法的XPath查询表达式。
    • 让我们分解这个表达式:
      • //: 这个符号表示选择文档中的所有匹配元素,而不仅仅是直接子元素。它在整个文档中进行搜索。
      • div: 这指定了想要选择的元素类型。在这种情况下,它是<div>元素。
      • [@class="info"]: 这是一个条件(谓语),用来进一步细化选择。它指定只选择那些具有class="info"属性的<div>元素。
        • @class: 表示选择元素的class属性。
        • "info": 表示属性值必须精确匹配info字符串。
  • 在网页的响应内容中选择所有class属性值为info<div>元素,并将这些元素存储在node_list变量中。这个列表可以被进一步用于抽取数据、分析或其他处理。在网页抓取和数据提取的过程中,这是一种非常常见的做法。
2. 判断并遍历节点
1
2
if node_list:
for node in node_list:
  • 首先检查 node_list 是否非空,确保有要处理的节点。
  • 然后遍历这些节点,每个节点 (node) 代表一个电影条目。
  • if node_list:
    • 这是一个条件语句,检查node_list是否非空。在Python中,空列表([])被认为是False,而非空列表被认为是True
    • 这个条件确保仅当node_list中有元素时,即列表不为空时,才会执行下面的代码块。这是一个良好的编程实践,可以避免在空列表上执行操作时出现错误。
  • for node in node_list:
    • 这是一个for循环,用于遍历node_list中的每个元素。
    • 在每次迭代中,node变量会被赋予node_list中的当前元素。
    • 如果node_list是由之前提到的response.xpath('//div[@class="info"]')表达式返回的,那么每个node很可能是一个表示HTML <div>元素的对象。这种对象通常提供了进一步提取数据(如文本内容、属性等)的方法。
  • 如果node_list不为空,即至少包含一个元素,那么对于列表中的每个元素(每个<div class="info">),执行循环体中的代码。循环体的具体内容没有提供,但通常它会包含进一步处理每个node的代码,例如提取信息、打印数据、存储结果等。
3. 提取电影信息
1
2
3
movie_name = node.xpath('./div/a/span/text()').get()
director = node.xpath('./div/p/text()').get().strip()
score = node.xpath('.//span[@class="rating_num"]/text()').get()
  • 提取电影名称:使用 XPath 查询从当前节点 (node) 中提取电影名称。
  • 提取导演信息:获取导演信息,这里使用 .strip() 来移除字符串开头和结尾的空白字符。
  • 提取电影评分:获取电影评分,这里使用 .// 从当前节点的所有子孙节点中查找符合条件的节点。
  • movie_name = node.xpath('./div/a/span/text()').get()
    • 这行代码从当前的node中提取电影名称。
    • ./div/a/span/text(): XPath查询语句。
      • ./ 表示从当前节点开始。
      • div/a/span 定位到一个<span>元素,这个元素是一个<a>元素的子元素,而<a>元素又是一个<div>元素的子元素。
      • /text() 选择这个<span>元素的文本内容。
    • .get() 是一个方法,用来获取XPath查询结果的第一个匹配项。
  • director = node.xpath('./div/p/text()').get().strip()
    • 这行代码用来提取导演的名称。
    • ./div/p/text(): XPath查询语句。
      • 类似于前面的查询,但这次是选择一个<p>元素的文本内容。
    • .get() 同样获取第一个匹配项的文本。
    • .strip() 是一个字符串方法,用来移除字符串首尾的空白字符(如空格、换行符等)。
  • score = node.xpath('.//span[@class="rating_num"]/text()').get()
    • 这行代码用于提取电影评分。
    • .//span[@class="rating_num"]/text(): XPath查询语句。
      • .// 表示在当前节点及其子节点中查找。
      • span[@class="rating_num"] 选择所有class属性为rating_num<span>元素。
      • /text() 同样选择这些<span>元素的文本内容。
    • .get() 获取第一个匹配结果。
  • 这三行代码是在遍历一个包含多个节点(每个节点代表一个电影信息)的列表时,用来从每个节点中提取特定信息的标准操作。这种方式在爬虫和数据提取的过程中非常常见,特别是当处理诸如电影列表页面这样的结构化数据时。
4. 创建并填充 Scrapy Item
1
2
3
4
item = DbItem()
item["movie_name"] = movie_name
item["director"] = director
item["score"] = score
  • item = DbItem()
    • 这行代码创建了一个DbItem的实例。通常在Scrapy项目中定义,用于表示提取的数据结构。
    • 在Scrapy中,Item对象用于定义存储爬取数据的结构。它们类似于Python中的字典,但提供额外的保护和便利,如字段定义和类型检查。
  • item["movie_name"] = movie_name
    • 这行代码将之前提取的movie_name(电影名称)赋值给item"movie_name"键。
    • 这意味着DbItem类中定义了一个字段movie_name,用于存储电影名称。
  • item["director"] = director
    • 类似地,这行代码将提取的director(导演名称)赋值给item"director"键。
  • item["score"] = score
    • 同样地,这行代码将提取的score(电影评分)赋值给item"score"键。
  • 创建了一个DbItem实例,并填充了从网页中提取的数据:电影名称、导演和评分。在Scrapy中,这样的item对象通常会被进一步传递到管道(pipelines),在那里可以进行如存储到数据库、进行数据清洗或其他处理的操作。使用Item对象的好处在于它提供了结构化的数据存储方式,有助于维护代码的清晰度和可维护性。
5. 请求电影详情页
1
2
detail_url = node.xpath('./div/a/@href').get()
yield scrapy.Request(detail_url, callback=self.get_detail, meta={"info":item})
  • detail_url = node.xpath('./div/a/@href').get()
    • 这行代码使用XPath选择器从当前处理的节点(node)中提取详情页面的链接。
    • ./div/a/@href: XPath查询语句。
      • ./ 表示从当前节点开始。
      • div/a 定位到当前节点下的<div>元素中的<a>元素。
      • @href 获取这个<a>元素的href属性,即链接地址。
    • .get() 方法从XPath选择器返回的结果中获取第一个匹配项,也就是详情页面的URL。
  • yield scrapy.Request(detail_url, callback=self.get_detail, meta={"info":item})
    • 这行代码创建一个新的Scrapy请求(Request),用于爬取在detail_url中找到的URL。
    • scrapy.Request(detail_url, ...) 创建一个新的请求对象,其目标是detail_url
    • callback=self.get_detail 设置当请求得到响应时应调用的回调方法为get_detail。这意味着,当Scrapy成功访问detail_url并获得响应时,它将自动调用get_detail方法来处理响应。
    • meta={"info":item} 将一个额外的信息字典{"info": item}传递给回调方法。这通常用于在不同请求之间传递数据。在这里,item对象(可能包含已经提取的某些数据,如电影名、导演、评分等)被传递到详情页面的处理方法中。
  • 在Scrapy中,yield关键字被用于生成请求而不是直接返回它们。这允许Scrapy处理请求队列,并根据可用的资源和设置来调度这些请求。这种方法使得爬虫能够有效地管理大量的并行请求,同时避免过载目标服务器或被封禁。

信息

在编程中,特别是在异步编程和事件驱动编程中,”callback”(回调函数)是一个非常重要的概念。回调函数是传递给另一个函数或方法的函数,它在那个函数或方法执行完某些操作之后被调用。在不同的编程语言和框架中,回调函数的具体实现和使用方式可能有所不同,但其基本概念是相似的。

回调函数的基本原理

  1. 定义回调函数: 回调函数是定义的一个函数,它将在未来的某个时间点被调用。这个函数通常定义了在某些操作完成后应该执行的操作。
  2. 将回调函数作为参数传递: 将这个回调函数作为参数传递给另一个函数或方法。这通常发生在希望在那个函数完成其主要操作后执行一些额外操作的情况下。
  3. 异步操作或事件处理: 在进行异步操作(如网络请求、文件读写等)或处理事件(如用户输入、定时器触发等)时,回调函数特别有用。在这些情况下,不能立即得到结果,回调函数提供了一种在操作完成时得到通知并执行相关代码的方式。

回调函数的例子

在Python中,回调函数的一个常见例子是在多线程或网络请求中使用:

1
2
3
4
5
6
7
8
9
10
import requests

def on_success(response):
print("Success:", response.text)

def on_error(error):
print("Error:", error)

# 发送异步HTTP请求
requests.get("https://www.example.com", success=on_success, error=on_error)

在这个例子中,on_successon_error是回调函数。它们分别在HTTP请求成功时和出错时被调用。

在Scrapy中的回调函数

在Scrapy这类网络爬虫框架中,回调函数用于处理从网页请求中返回的响应。例如:

1
2
3
4
5
6
7
8
9
10
11
import scrapy

class MySpider(scrapy.Spider):
name = 'example_spider'

def start_requests(self):
yield scrapy.Request(url="https://www.example.com", callback=self.parse)

def parse(self, response):
# 处理响应的代码
pass

在这里,parse方法是一个回调函数,当Scrapy框架从指定的URL接收到响应时调用。

总的来说,回调函数是一种在某个操作完成后(通常是异步操作)再执行的函数。它们在事件驱动的程序设计中尤为重要,允许程序在等待一个操作完成时继续执行其他任务,并在适当的时候处理操作结果。

6. 分页处理
1
2
3
self.page_num += 1
page_url = 'https://movie.douban.com/top250?start={}&filter='.format(self.page_num*25)
yield scrapy.Request(page_url, callback=self.parse)
  • self.page_num += 1
    • 这行代码将类属性page_num的值增加1。这个属性用于追踪当前爬取的页数。在每次处理完一个页面后,递增page_num以移至下一个页面。
  • page_url = 'https://movie.douban.com/top250?start={}&filter='.format(self.page_num*25)
    • 这里构造了下一个页面的URL。
    • 'https://movie.douban.com/top250?start={}&filter='是基本的URL格式。豆瓣电影Top 250页面的分页通过URL参数start实现,其中start表示列表的起始位置。
    • .format(self.page_num*25)用于插入计算后的起始位置。由于每页显示25部电影,那么每递增一页,start的值应增加25。例如,第一页是从0开始(0*25),第二页是从25开始(1*25),依此类推。
  • yield scrapy.Request(page_url, callback=self.parse)
    • 这行代码生成了一个新的Scrapy请求,用于爬取下一个页面。
    • scrapy.Request(page_url, callback=self.parse)创建一个新的请求对象,其目标是page_url
    • callback=self.parse指定当请求得到响应时,应该调用parse方法来处理该响应。parse是Scrapy爬虫中的标准方法,用于处理和解析响应内容。
    • 使用yield关键字来生成请求对象,允许Scrapy根据需要来调度和处理这些请求。
  • 在Scrapy中,适当地管理分页是提取多页数据的关键。代码正确地实现了递增页码并构建新页面URL的逻辑。通过在parse方法中重复这个过程,爬虫可以遍历并爬取整个列表的所有页面。
7. 处理结束
1
2
else:
return
  • 如果 node_list 为空,说明没有更多的电影信息可以处理,函数返回并结束。
  • 如果条件不满足(else部分),return语句会被执行。在Python中,return语句用于从函数返回。如果return后没有任何值或表达式,它默认返回None。在这个上下文中,它表示不再继续生成新的请求,爬虫的这个部分将停止执行。

这个 parse 方法展示了如何在 Scrapy 中处理分页和详情页的链接提取、数据抓取和传递。它有效地结合了 XPath 选择器、Scrapy Item 的使用,以及 Scrapy 的请求和响应机制。

详情页解析方法 get_detail
1
2
3
4
5
6
7
8
9
def get_detail(self, response):
item = DbItem()
info = response.meta.get("info")
item.update(info)
description_list = response.xpath('//div[@id="link-report"]/span[@class="all hidden"]/text()|//div[@id="link-report"]/span[@property="v:summary"]/text()').getall()
description = ''.join([des.strip() for des in description_list])

item["description"] = description
yield item
0. 方法定义
1
def get_detail(self, response):
  • 这是一个在 Scrapy 爬虫中定义的 get_detail 方法,它是一个回调函数,用于处理电影详情页的响应。
  • response 参数是 Scrapy 传递给这个方法的响应对象,包含了详情页的数据。
  • 这个方法定义表明get_detail是一个实例方法,用于处理从一个特定的请求返回的响应。这个方法接收一个参数response,它包含了对应请求的HTTP响应。
1. 初始化和更新 Item
1
2
3
item = DbItem()
info = response.meta.get("info")
item.update(info)
  • item = DbItem():创建一个 DbItem 对象。DbItem 通常是一个在 Scrapy 项目中定义的 Item 类,用于结构化存储提取的数据。继承自scrapy.Item,用于指定希望收集的数据字段。
  • info = response.meta.get("info"):从响应的 meta 属性中获取传递过来的电影基本信息。这些信息之前在处理列表页时已经被提取并通过 meta 参数传递到这个方法。这里从response.meta中提取了之前传递的元数据。response.meta是一个字典,用于在Scrapy的不同请求之间传递数据。在这个例子中,它被用来传递之前页面上已经抓取的数据。
  • item.update(info):将获取到的电影基本信息更新到 item 对象中。这行代码将从上一个页面提取的数据(存储在info字典中)合并到新创建的item对象中。
2. 提取电影描述信息
1
2
description_list = response.xpath('//div[@id="link-report"]/span[@class="all hidden"]/text()|//div[@id="link-report"]/span[@property="v:summary"]/text()').getall()
description = ''.join([des.strip() for des in description_list])
  • response.xpath() 方法用于执行XPath查询。它从响应的HTML中选择指定的元素。
  • XPath查询字符串有两部分,通过(并集运算符)连接:
    • //div[@id="link-report"]/span[@class="all hidden"]/text(): 选取所有id"link-report"<div>元素中,类名为"all hidden"<span>子元素的文本内容。
    • //div[@id="link-report"]/span[@property="v:summary"]/text(): 选取所有id"link-report"<div>元素中,property属性为"v:summary"<span>子元素的文本内容。
  • .getall() 方法获取所有匹配的元素的文本内容,返回一个字符串列表
  • description_list中的所有文本片段合并成一个单一的字符串。
  • [des.strip() for des in description_list] 是一个列表推导式,用于遍历description_list中的每个元素(des),并对每个元素应用.strip()方法。.strip()方法移除字符串两端的空白字符(包括空格、换行符等)。
  • ''.join([...]) 方法将经过清洁的字符串列表连接成一个单一的字符串。空字符串''作为连接符,意味着直接将文本片段连在一起,没有额外的字符插入。
  • 这段代码的作用是从HTML响应中提取电影描述,可能包括多个文本片段,并将它们清洁和合并为一个完整的描述字符串。这种方法在处理包含多个文本块或可选文本块(例如有些电影可能只有一种描述格式)的HTML页面时非常有用。
3. 设置描述并返回 Item
1
2
item["description"] = description
yield item
  • item["description"] = description
    • 这行代码将之前提取并合并的描述文本(description)赋值给item对象的"description"字段。
    • item是一个类似于字典的对象,用于存储提取的数据。在Scrapy中,这通常是一个继承自scrapy.Item的类的实例,用来定义和存储爬取的数据结构。
    • 这里,"description"字段是之前定义在DbItem(或类似的Item类)中的一个字段,用于保存电影的描述文本。
  • yield item
    • 使用yield关键字来产生item对象,将其传递给Scrapy的管道系统(Pipelines)。
    • 在Scrapy中,yield的使用允许框架接管并异步处理这些item对象。这些item随后会通过定义好的管道进行处理,例如进行数据清洗、验证、存储到数据库等操作。
    • 这种基于生成器的方法使得Scrapy能够有效地处理大量数据,并允许多个item同时在管道中进行处理,从而提高整个爬取和数据处理的效率。

这两行代码的作用是将提取的描述文本保存到一个item对象中,并将这个对象传递给Scrapy的后续处理流程,如管道处理。这是Scrapy爬虫中处理和传递数据的常见模式。

Items 文件

定义了一个 Scrapy Item 类,名为 DbItem,用于在 Scrapy 爬虫中存储和组织爬取的数据。

1
2
3
4
5
6
7
8
9
import scrapy

class DbItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
movie_name = scrapy.Field()
director = scrapy.Field()
score = scrapy.Field()
description = scrapy.Field()
  • import scrapy: 导入 Scrapy 模块,这是使用 Scrapy 框架的基础。
  • class DbItem(scrapy.Item): 定义了一个名为 DbItem 的类,该类继承自 scrapy.Item。在 Scrapy 中,Item 类用于定义一种数据结构,使得爬取的数据可以被结构化和标准化。
  • movie_name = scrapy.Field(): 定义了一个字段 movie_name 来存储电影的名称。使用 scrapy.Field() 表示这是一个用于存储数据的字段。
  • director = scrapy.Field(): 定义了一个字段 director 用于存储电影的导演信息。
  • score = scrapy.Field(): 定义了一个字段 score 用于存储电影的评分。
  • description = scrapy.Field(): 定义了一个字段 description 用于存储电影的详细描述。
  • 在 Scrapy 爬虫中,Item 类被用来收集和整理爬取的数据。例如,在爬取一个电影网站时,可以使用 DbItem 实例来存储每部电影的相关信息。这样的数据结构化处理使得数据的存储、输出和后续处理变得更加方便和标准化。

DbItem 类是 Scrapy 项目中用于定义和存储特定数据结构的方式。它允许爬虫以一种清晰和一致的方式处理爬取的数据。在这个例子中,DbItem 用于存储电影名称、导演、评分和描述等信息,从而使得数据在 Scrapy 爬虫的整个流程中易于处理和维护。

Pipelines 文件

定义了一个 Scrapy 的 Pipeline 类,名为 DbPipeline,用于处理由爬虫提取的数据。在 Scrapy 中,Pipeline 用于数据的清洗、验证和存储。

1
2
3
4
5
6
7
8
9
10
11
12
import json

class DbPipeline:
def open_spider(self, spider):
self.f = open('film.txt','w', encoding='utf-8')
def process_item(self, item, spider):
json_str = json.dumps(dict(item), ensure_ascii=False) + '\n'
self.f.write(json_str)
return item

def close_spider(self, spider):
self.f.close()
1. 初始化及打开文件
1
2
def open_spider(self, spider):
self.f = open('film.txt', 'w', encoding='utf-8')
  • class DbPipeline: 定义了一个名为DbPipeline的新类。

  • def open_spider(self, spider): 这个open_spider方法在爬虫开始时被调用。用于进行一些初始化工作。

  • self.f = open('film.txt','w', encoding='utf-8'): 这个方法打开一个名为film.txt的文件用于写入(’w’模式),并且指定编码为UTF-8。这是为了确保可以正确写入包含非ASCII字符(如中文)的文本。

  • 文件对象被赋值给self.f,这样在这个类的其他方法中也能使用这个文件对象。

2. 处理并存储数据
1
2
3
4
def process_item(self, item, spider):
json_str = json.dumps(dict(item), ensure_ascii=False) + '\n'
self.f.write(json_str)
return item
  • def process_item(self, item, spider): 这个方法会被 Scrapy 框架自动调用,每次爬虫提取出一个 item 时都会执行。process_item方法是管道处理每个item的核心方法。
  • json_str = json.dumps(dict(item), ensure_ascii=False) + '\n': 将 item 对象转换成 JSON 字符串。ensure_ascii=False 参数确保非 ASCII 字符(如中文)被正确处理。
  • self.f.write(json_str): 将 JSON 字符串写入之前打开的文件。然后,这个JSON字符串(json_str)被写入到前面打开的文件中。每个item占一行,因为在字符串末尾添加了换行符\n
  • return item: 返回 item,以便它能够传递到其他可能存在的 pipeline 或最终输出。
3. 关闭文件
1
2
def close_spider(self, spider):
self.f.close()
  • def close_spider(self, spider): 在爬虫结束时被调用。当爬虫关闭时,Scrapy框架会调用close_spider方法。
  • self.f.close(): 关闭打开的文件。这是一个重要的步骤,确保数据被完整写入并且文件正常关闭。这个方法关闭了文件对象self.f。这是一个良好的实践,可以确保所有数据都被写入到文件中,并且文件正确地关闭。

在 Scrapy 项目中,Pipeline 类用于处理从爬虫传递过来的数据。在这个例子中,DbPipeline 负责将每个电影的信息以 JSON 格式保存到一个文本文件中。这样的处理方式适合于数据的持久化存储,例如保存到文件、数据库等。

DbPipeline 类演示了 Scrapy 爬虫中如何使用 pipeline 对提取的数据进行进一步的处理和存储。通过在爬虫启动和关闭时执行相关操作,并在提取数据时进行处理,pipeline 为数据的后续使用提供了一个有效的方式。

Settings 文件

settings.py 是 Scrapy 项目的配置文件,用于定义爬虫的各种设置。这些设置影响着爬虫的行为方式。

此处删除了大部分注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding: utf-8 -*-

# Scrapy settings for db project

BOT_NAME = 'db'

SPIDER_MODULES = ['db.spiders']
NEWSPIDER_MODULE = 'db.spiders'

# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'db (+http://www.yourdomain.com)'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36"
}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
'db.pipelines.DbPipeline': 300,
}
基本设置
  • BOT_NAME = 'db': 定义了爬虫项目的名称,这里是 'db'
  • SPIDER_MODULES = ['db.spiders']: 指定了包含 Scrapy 爬虫的模块。
  • NEWSPIDER_MODULE = 'db.spiders': 指定了新爬虫的模板搜索路径。
用户代理设置
  • DEFAULT_REQUEST_HEADERS: 这是默认的请求头部设置。在这里,可以设置 AcceptAccept-Language 头部,以及用户代理 (User-Agent)。这有助于模仿常规浏览器请求,避免被目标网站识别为爬虫。
  • ROBOTSTXT_OBEY = False: 这个设置告诉 Scrapy 是否遵守 robots.txt 规则。在这里,它被设置为 False,意味着爬虫将忽略目标网站的 robots.txt 规则。
Item Pipeline 设置
  • ITEM_PIPELINES: 这个设置定义了项目中启用的 Item Pipeline。在这里,DbPipeline 被设置为处理 item 的 pipeline,300 表示其优先级。

爬虫项目的目标和请求流程总结

目标数据
  • 目标是爬取电影信息以及从次级页面(详情页)获取电影简介。
请求流程
  • 访问一级页面:爬取电影列表页,提取电影的基本信息和指向详情页的 URL。
  • 访问次级页面:访问每部电影的详情页,进一步提取电影的详细简介。
数据存储和一致性
  • 由于 Scrapy 是异步的,页面响应的顺序可能与请求发送的顺序不同。因此,使用 meta 参数在请求间传递数据,保证了数据(如电影的基本信息和详细简介)之间的一致性和关联。

项目案例

分析需求

  1. 获取腾讯社会招聘的岗位信息

  2. 要获取信息的URL并不会显示存在页面中因此要通过开发者模式查看,能发现其有众多异步请求且要获取的信息就在其中

  3. 分析排前的请求,在Fetch/XHR下的Preview可以发现图示请求的数据结构与页面内容近似,因此该页面的URL就应该在这个请求的标头中

  4. 通过标头构造页面即可开始实施抓取数据,这个URL可以发现拼接了很多参数

分析规律

  1. 通过分析每页的URL,发现切换页面的规律
1
2
URL_1 = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702285787290&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=1&pageSize=10&language=zh-cn&area=au'
URL_2 = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702289736331&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex=2&pageSize=10&language=zh-cn&area=au'

可以发现pageIndex是不一样的,一个为1一个为2,那么后续只要利用花括号pageSize={}传值即可实现页面跳转

同时可以发现URL都存在api因此其都是为接口数据

爬取思路

  1. 找到列表页的数据的url
  2. 使用scrapy.Request 方法进行请求
  3. 解析第二步的响应,提取里面的内容

步骤

1. 创建项目

首先在终端输入 scrapy startproject tx 其中tx代表项目名称为腾讯缩写

2. 创建爬虫

然后输入scrapy genspider tencent_data careers.tencent.com 创建爬虫tencent_data 其爬取域名为careers.tencent.com

3. 编辑爬虫

在生成tencent_data.py的爬虫文件下继承自scrapy.Spider命名为TencentDataSpider的类中编辑需要构造的URL,在原本的URL内修改参数pageIndex=1pageIndex={}以便分页(页面跳转),将url_data格式化字符串中的pageIndex参数设为1,生成第一页的URL,作为爬虫开始爬取的起始点。

1
2
3
4
5
6
7
8
9
10
11
12
13
import scrapy


class TencentDataSpider(scrapy.Spider):
name = "tencent_data"
allowed_domains = ["careers.tencent.com"]

url_data = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702285787290&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=au'

start_urls = [url_data.format(1)]

def parse(self, response):
pass

4. 处理列表列

parse方法中添加一个循环以确定要爬取的页面数量,可以通过循环for来进行。parse 是Scrapy爬虫中默认的回调方法,用于处理响应。该方法通过循环生成接下来四页的URL(从第1页到第4页)。对于每一页,它创建并产生一个新的Scrapy请求 (scrapy.Request),并指定回调函数为self.parse_data

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import scrapy


class TencentDataSpider(scrapy.Spider):
name = "tencent_data"
allowed_domains = ["careers.tencent.com"]

url_data = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702285787290&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=au'

start_urls = [url_data.format(1)]

def parse(self, response):
for i in range(1, 5):
next_page_url = self.url_data.format(i)
yield scrapy.Request(next_page_url, callback=self.parse_data)

注意

在提供的parse方法中,response参数实际上没有被使用。这是因为该方法的主要目的是生成后续页面的URL,并对每个URL发起新的请求,而不是处理response中的数据。

在常规的Scrapy爬虫中,parse方法通常用于处理响应,从中提取数据或发现新的URL来跟进。但在这个特定的例子中,parse方法仅用于根据初始响应生成一系列后续页面的请求。实际处理这些页面的响应内容的任务被委托给了另一个方法parse_data(在后续定义)。这意味着在这个特定的parse实现中,初始的response对象不包含需要立即处理的相关数据,其主要作用是触发爬虫开始执行后续页面的请求。

显然请求的文件是JSON文件,因此需要编辑方法处理从网页请求中返回的JSON格式的响应。这个方法特别适用于处理返回JSON数据的API响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import scrapy


class TencentDataSpider(scrapy.Spider):
name = "tencent_data"
allowed_domains = ["careers.tencent.com"]

url_data = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702285787290&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=au'

start_urls = [url_data.format(1)]

def parse(self, response):
for i in range(1, 5):
next_page_url = self.url_data.format(i)
yield scrapy.Request(next_page_url, callback=self.parse_data)
def parse_data(self, response):
dict_data = response.json() # json.loads(response.text)
print(dict_data)
  • parse_data是一个实例方法,与parse方法类似,它是Scrapy框架用于处理响应的回调函数。不同的是,这个方法专门用于处理JSON响应。
  • response参数是一个包含HTTP响应数据的对象。在Scrapy中,这个对象通常包括响应的内容和其他元数据。
  • dict_data = response.json()这一行是核心功能。response.json()方法将响应的内容(假定是JSON格式)解析成Python字典。这允许以Python原生的方式访问JSON响应中的数据。
  • 在将JSON响应解析为字典后,可以进一步处理这些数据。例如从字典中提取特定的字段,并将它们存储在Scrapy的Item对象中,或者直接进行数据清洗、转换等操作。

在修改settings.py配置文件的参数为正确后进行调试,发现其response依旧能返回200,然而实际输入的URL和抓取时间戳timestamp并不一样,这说明其对时间戳并不敏感。

如果敏感且失败则需要构造时间戳int(time.time() * 1000)

进行分析调试结果response.json() 返回的数据结构是一个字典且具有数据

但是由于这样数据有太多且杂乱无章,因此在获取数据的时候就进行部分的过滤,只获取Data下的Posts里的部分数据即可,例如名称,地址,时间,详情URL等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import scrapy


class TencentDataSpider(scrapy.Spider):
name = "tencent_data"
allowed_domains = ["careers.tencent.com"]

url_data = 'https://careers.tencent.com/tencentcareer/api/post/Query?timestamp=1702285787290&countryId=&cityId=&bgIds=&productId=&categoryId=&parentCategoryId=&attrId=&keyword=&pageIndex={}&pageSize=10&language=zh-cn&area=au'

PostURL = 'https://careers.tencent.com/tencentcareer/api/post/ByPostId?timestamp=1701864924030&postId={}&language=zh-cn'


start_urls = [url_data.format(1)]

def parse(self, response):
for i in range(1, 5):
next_page_url = self.url_data.format(i)
yield scrapy.Request(next_page_url, callback=self.parse_data)
def parse_data(self, response):
dict_data = response.json().get("Data").get("Posts") # json.loads(response.text)
for data in dict_data:
item = {}
item['RecruitPostName'] = data.get('RecruitPostName')
item['LocationName'] = data.get('LocationName')
item['LastUpdateTime'] = data.get('LastUpdateTime')
item['CategoryName'] = data.get('CategoryName')
PostId = data.get('PostId')
detail_url = self.PostURL.format(PostId)
yield scrapy.Request(detail_url, callback=self.parse_post_detail, meta={'info': item})
  • response.json()将返回的JSON数据转换为Python字典。
  • .get("Data").get("Posts")从这个字典中获取Data键对应的值,然后从Data中获取Posts键对应的值。JSON结构是{"Data": {"Posts": [...]}},这将得到包含职位信息的列表。
  • 遍历dict_data列表中的每个元素(每个元素代表一个职位)。
  • 对于每个职位,创建一个新的字典item,并从data中提取相关字段。
  • 使用PostId来格式化self.PostURL字符串,以创建指向每个职位详情页的URL。
  • item = {}: 这行代码创建了一个空字典item,用于存储从每个职位信息中提取的数据。这是一种常见的在爬虫中收集数据的方法。
  • self.PostURL是在访问当前类实例(在这种情况下是TencentDataSpider的实例)的PostURL属性。这意味着PostURL是这个类的实例属性,而非一个局部变量或一个类变量(类变量将会使用类名来访问,比如TencentDataSpider.PostURL)。
  • data字典中获取PostId值,然后使用这个PostId来格式化self.PostURL字符串。
  • PostURLself.PostURL.format(PostId)使用了format方法来插入PostId进行构造
  • 格式化后的URL被存储在detail_url变量中。然后,这个URL被用来生成一个新的请求,该请求被发送到parse_post_detail方法(这是一个假定的方法,用于处理每个职位详情页面的响应)
    • scrapy.Request:
      • 这是Scrapy用来创建新HTTP请求的类。
      • detail_url是这个新请求的目标URL。这个URL通常是从当前页面提取的,指向一个需要进一步爬取的页面。
    • callback=self.parse_post_detail:
      • callback参数指定了Scrapy在收到此请求的响应后应该调用的方法。
      • self.parse_post_detail是定义的爬虫类中的一个方法。当Scrapy处理detail_url的响应时,它会将响应数据传递给parse_post_detail方法。
    • meta={'info': item}:
      • meta是一个字典,用于在Scrapy的不同请求之间传递额外的数据。
      • 在这个例子中,meta字典包含一个键'info',其值为item。这意味着可以在parse_post_detail方法中通过response.meta['info']来访问这个item对象。
      • 这种方式在请求链中保持状态或传递数据时非常有用。
    • yield:
      • 使用yield关键字来生成请求对象。在Scrapy中,yield的使用使得框架可以接管并异步处理这些请求。
      • 这允许Scrapy根据需要进行请求的调度,而不是立即发出请求,从而更有效地管理网络资源和避免对目标网站造成过大压力。

5. 处理详情页和Items 文件

随后定义parse_post_detail方法和构造items.py文件下的类属性

tencent_data.py:

1
2
3
4
5
6
7
8
9
def parse_post_detail(self, response):
item = TxItem()
info = response.meta.get('info')
item.update(info)
dict_data = response.json()
item['Requirement'] = dict_data.get("Data").get('Requirement').replace('\r\n', '')
item['Responsibility'] = dict_data.get("Data").get('Responsibility').replace('\r\n', '')

yield item

items.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class TxItem(scrapy.Item):
# define the fields for your item here like:
# name = scrapy.Field()
RecruitPostName = scrapy.Field()
LocationName = scrapy.Field()
LastUpdateTime = scrapy.Field()
CategoryName = scrapy.Field()
Requirement = scrapy.Field()
Responsibility = scrapy.Field()

然后调试检查查看传值是否成功

调试处理详情页相关方法前需要同时配置Items 文件,不然无法传值

6. 编辑Pipelines

Settings.py文件激活Pipelines后,配置Pipelines文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from itemadapter import ItemAdapter


class TxPipeline:
def process_item(self, item, spider):
with open('tencent.txt', 'a', encoding='utf-8') as f:
f.write(str(item))
return item

在Scrapy的管道(Pipeline)中,可以选择用不同的方式来处理文件的打开和关闭。

  1. 类定义:
    • class TxPipeline: 定义了一个名为TxPipeline的新类。
    • 这个类是Scrapy的一个管道,它必须实现process_item方法。
  2. process_item方法:
    • def process_item(self, item, spider): 是管道处理每个爬取的项目(item)的方法。
    • 每当爬虫提取出数据并生成item对象时,Scrapy框架会自动调用这个方法,并将item对象和spider对象作为参数传递给它。
  3. 文件写入操作:
    • with open('tencent.txt', 'a', encoding='utf-8') as f: 使用with语句打开文件tencent.txt。如果文件不存在,将会创建它。文件以追加模式('a')打开,意味着新写入的内容会被添加到文件的末尾,而不是覆盖原有内容。文件以utf-8编码打开,这对于写入非ASCII字符(例如中文)很重要。
    • f.write(str(item))item对象转换为字符串,并写入文件。这里需要注意的是,str(item)可能不会以最优雅的格式输出,特别是如果item包含多个字段或嵌套结构时。在实际应用中,可能需要更精细的格式化方法,比如使用json.dumps来生成更可读的JSON格式。
  4. 返回项目:
    • return item:在处理完项目后,管道返回item对象。这允许同一个item被多个管道依次处理。在Scrapy中,项目(items)可以通过多个管道传递,每个管道都可以执行一些操作(如清洗数据、去重、写入数据库等)。

使用with语句:

TxPipeline类中,process_item方法使用了with语句来打开文件:

1
2
with open('tencent.txt', 'a', encoding='utf-8') as f:
f.write(str(item))
  • with语句:Python中的with语句用于包裹执行需要资源管理的代码块,比如文件操作。with语句可以确保文件在使用完毕后被正确关闭,即使在处理文件过程中发生错误也是如此。
  • 优点:使用with语句的主要优点是它会自动处理文件的关闭。这种方式在每次process_item被调用时打开文件,写入数据,然后立即关闭文件,非常适合写入少量数据。
  • 缺点:如果process_item被频繁调用,这种方式可能会导致性能问题,因为每次调用都会打开和关闭文件。

使用open_spiderclose_spider方法:

在之前的案例DbPipeline类中,文件的打开和关闭是在open_spiderclose_spider方法中处理的:

1
2
3
4
5
def open_spider(self, spider):
self.f = open('dbtest1result.txt', 'w', encoding='utf-8')

def close_spider(self, spider):
self.f.close()
  • open_spiderclose_spider方法:这两个方法分别在爬虫开始时和结束时被调用。在这里,文件在爬虫启动时被打开,并在爬虫关闭时被关闭。
  • 优点:这种方式对于处理大量数据更有效,因为它在整个爬虫过程中只打开和关闭文件一次。这减少了文件操作的开销,提高了性能。
  • 缺点:如果爬虫在执行过程中出现异常并意外终止,可能导致文件没有正确关闭。这可能会导致数据丢失或文件损坏。
  • 如果爬虫产生的数据量不大,或者更关心代码的简洁性,使用with语句是一个很好的选择。
  • 如果爬虫产生大量数据,或者希望减少文件操作的开销,使用open_spiderclose_spider方法可能更合适。

检查

最后运行,产出文件tencent.txt且具有相应的值则成功

数据库存储

首先在Ubuntu的MySQL创建一个数据库叫tx

mysql> create database tx;

随后检查是否创建成功mysql> show databases;:

然后切换数据库并检查内容:

use tx;

show tables;

检查无误后按照items.py文件下的TxItem类创建表格即可:

1
2
3
4
5
6
7
8
9
10
11
# 爬虫存数据  必须要先有表格才能进行数据的增加
create table txzp(
# 主键
id int primary key auto_increment,
RecruitPostName varchar(50),
LocationName varchar(50),
LastUpdateTime varchar(50),
CategoryName varchar(50),
Requirement text,
Responsibility text
)

创建表格后通过show tables;查看当前数据库的表格内容

使用desc txzp; 查看 txzp 表格内的内容属性

建立测试文件

tx_debug.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import pymysql

# 建立数据库连接
# host: 数据库服务器地址,这里设置为'localhost'表示数据库服务在本地
# port: 数据库服务端口,默认MySQL端口为3306
# user: 用户名,这里使用'yiuhang_test'
# password: 用户密码,这里为'Test123..'
# db: 要连接的数据库名,这里为'tx'
# charset: 设置字符集,这里使用'utf8'
conn = pymysql.connect(host='localhost',
port=3306,
user='yiuhang',
password='Test123..',
db='tx',
charset='utf8')

print(conn) # 打印连接对象,用于验证是否连接成功

# 创建一个cursor对象,用于执行查询和获取结果
curs = conn.cursor()

# 在此处添加数据库操作的CRUD(创建、读取、更新、删除)逻辑
# 例如:curs.execute("SELECT * FROM your_table")

# 提交事务,对于更改数据库的操作(INSERT, UPDATE, DELETE),需要调用commit()来提交更改
conn.commit()

# 关闭cursor和连接
curs.close()
conn.close()
  • 这段脚本主要用于建立与MySQL数据库的连接,执行一些数据库操作(CRUD逻辑),然后关闭连接。
  • pymysql.connect() 函数用于创建与MySQL数据库的连接。
  • conn.cursor() 方法创建一个游标对象,该对象可以用来执行SQL语句并获取结果。
  • 数据库的实际操作(如查询、插入数据等)需要在获取游标后进行。
  • 执行修改数据库内容的操作(如INSERT, UPDATE, DELETE)后,需要调用 conn.commit() 来提交这些更改。
  • 最后,使用 curs.close()conn.close() 关闭游标和连接,以释放资源。

注意

在运行测试文件前,如果使用虚拟机需要检查3306端口是否已经让虚拟机和主机映射,否则会产生 Mysql Python Connector No connection could be made because the target machine actively refused it 的报错。

当在虚拟机(例如使用VMware)中运行应用程序(如数据库服务器)并希望从主机(物理机器)访问时,需要进行端口映射。这是因为虚拟机通常运行在与主机隔离的网络环境中。端口映射确保从主机到虚拟机的网络通信能够正确进行。

解释端口映射

  1. 为什么需要端口映射:
    • 虚拟机通常在私有网络中运行,这意味着它们对主机网络而言是不可见的。
    • 端口映射使得主机可以通过指定的端口访问虚拟机中的服务。
  2. 示例解释:
    • MySQL(端口3306): 假设在虚拟机中运行MySQL服务器,它默认监听3306端口。要从主机上的应用程序连接到这个MySQL服务器,需要将虚拟机的3306端口映射到主机的某个端口(也可以是3306)。
    • SSH(端口22): 同理,如果想要通过SSH连接到虚拟机,需要将虚拟机的22端口映射到主机的某个端口(也可以是22)。

端口映射的步骤

  1. 打开VMware的虚拟网络编辑器。
  2. 选择用于虚拟机的网络适配器(通常是NAT模式)。
  3. 进入NAT设置,然后设置端口转发规则。
  4. 添加规则:指定主机端口,目标IP(虚拟机IP)和虚拟机端口。

如果是服务器而非虚拟机

  • 物理服务器:
    • 如果数据库运行在物理服务器上,通常不需要端口映射,因为服务器与本地机器或其他服务器都在同一网络或可通过互联网直接访问。
    • 需要注意的是安全设置,如防火墙规则,确保只有授权的客户端能够访问特定端口。
  • 云服务器:
    • 在云平台(如AWS、Azure)上,可能需要配置安全组或网络安全规则来允许特定端口(如3306)的流量。
    • 这类似于在物理服务器上设置防火墙规则。

在所有情况下,重要的是确保只有信任的客户端能够访问敏感服务(如数据库服务器),并且采用强密码和加密连接(如SSL)来保护数据安全。

配置步骤

1. 在 settings.py 中配置MySQL信息

在Scrapy项目的settings.py文件中,配置数据库连接信息。这是一个字典,包含连接数据库所需的所有信息。

1
2
3
4
5
6
7
8
9
10
11
12
# 配置mysql链接信息
DATABASE_CONFIG = {
'type': 'mysql', # 使用的是哪一个数据库类型
'config': {
'host': 'localhost', # 数据库服务器地址
'port': 3306, # 数据库服务器端口,MySQL默认为3306
'user': 'yiuhang', # 数据库用户名
'password': 'Test123..', # 数据库密码
'db': 'tx', # 数据库名
'charset': 'utf8', # 数据库编码
}
}
2. 在 pipelines.py 中创建MySQL存储的类

pipelines.py文件中,创建一个Pipeline类,该类负责在爬虫开始时连接数据库,在处理每个item时存储数据,以及在爬虫结束时关闭数据库连接。注释掉之前的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pymysql

class TxzpPipeline:
def open_spider(self, spider):
# 此方法在爬虫开启时调用
pass

def process_item(self, item, spider):
# 对每个爬取的item调用此方法
pass

def close_spider(self, spider):
# 此方法在爬虫关闭时调用
pass
3. 注册Pipeline

settings.py文件中,将Pipeline添加到ITEM_PIPELINES设置中。这里的数字表示优先级,范围是0到1000。注释掉之前的配置。

1
2
3
ITEM_PIPELINES = {
"tx.pipelines.TxzpPipeline": 300,
}
4. 编写连接MySQL的逻辑

TxzpPipeline类中,实现数据库连接和关闭逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import pymysql

class TxzpPipeline:
def open_spider(self, spider):
"""
在爬虫开启时调用此方法,用于建立数据库连接。
此方法从spider的设置中获取数据库配置信息,并建立连接。
"""

# 从爬虫的设置中获取数据库配置
data_config = spider.settings.get("DATABASE_CONFIG")

# 检查配置类型是否为MySQL,确保我们使用正确的配置
if data_config.get('type') == 'mysql':
# 使用提供的配置参数建立MySQL连接
self.conn = pymysql.connect(**data_config.get('config'))

# 创建一个数据库操作游标
self.cursor = self.conn.cursor()

def process_item(self, item, spider):
"""
对每个从爬虫爬取的item调用此方法。
这里可以添加将item保存到数据库的逻辑。
"""

# 示例:保存逻辑(需实现)
# sql = "INSERT INTO table_name (column1, column2) VALUES (%s, %s)"
# self.cursor.execute(sql, (item['field1'], item['field2']))
# self.conn.commit()

return item

def close_spider(self, spider):
"""
在爬虫结束时调用此方法,用于关闭数据库连接。
"""

# 关闭数据库操作游标
self.cursor.close()

# 关闭数据库连接
self.conn.close()
  1. open_spider 方法
    • 在爬虫启动时被Scrapy框架自动调用。
    • spider的设置 (settings.py) 中获取名为DATABASE_CONFIG的数据库配置信息。
    • 如果配置类型是MySQL('type': 'mysql'),则使用这些配置信息(如主机、端口、用户名、密码等)建立数据库连接。
    • 创建一个用于执行数据库操作的游标。
    • 方法定义:open_spider(self, spider)
      • 作用open_spider是一个特殊的Scrapy方法,它在爬虫启动时自动调用。这是设置爬虫运行前所需资源的理想位置,例如建立数据库连接。
      • 参数self指向类的实例,而spider是当前运行的爬虫实例。通过spider,可以访问Scrapy项目的设置和其他与爬虫相关的属性。
    • 获取数据库配置:data_config = spider.settings.get("DATABASE_CONFIG")
      • 作用:这一行从Scrapy的settings.py文件中获取数据库配置信息。将配置放在settings.py中可以集中管理配置,并在需要时轻松更改,而无需修改代码本身。
      • 为什么重要:这种做法增加了代码的灵活性和可维护性,因为可以在不同环境(如开发、测试、生产)中使用不同的数据库配置,而无需更改代码。
    • 检查配置类型:if data_config.get('type') == 'mysql':
    • 作用:这个条件检查确保从settings.py中获取的配置是为MySQL数据库设计的。这是一个安全措施,以防配置被错误地设置。
    • 为什么重要:在可能有多种数据库配置的情况下(例如MySQL、PostgreSQL等),这个检查确保Pipeline使用正确的配置。这有助于避免运行时错误和配置混淆。
    • 建立MySQL连接:self.conn = pymysql.connect(**data_config.get('config'))
      • 作用:这行代码使用pymysql模块建立到MySQL数据库的连接。**data_config.get('config')是一个字典解包操作,它将配置字典中的键值对作为参数传递给pymysql.connect函数。
      • 为什么重要:建立数据库连接是进行数据库操作的首要步骤。使用pymysql模块可以方便地与MySQL数据库交互。这种方法的优点是可以直接从配置文件中读取连接信息,提高了代码的可读性和可维护性。
    • 创建数据库游标:self.cursor = self.conn.cursor()
      • 作用:创建一个数据库游标对象,该对象允许在数据库中执行SQL命令并处理结果。
      • 为什么重要:游标是执行和管理数据库操作的关键。它不仅允许执行SQL语句,还能帮助有效地检索查询结果。在Scrapy中,通常会在process_item方法中使用此游标执行数据库操作。
  2. process_item 方法
    • 对爬虫爬取的每个item调用此方法。
    • 这是数据处理和保存的主要地方。在这个方法中,可以编写将爬取的数据(item)保存到MySQL数据库的逻辑。
    • 在这个示例中,该方法仅返回item,需要根据具体需求来实现数据插入的逻辑。
  3. close_spider 方法
    • 在爬虫结束时被Scrapy框架自动调用。
    • 关闭数据库游标和连接,确保释放资源。
5.编写存储逻辑

实现process_item方法以将每个item存储到MySQL数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
def process_item(self, item, spider):
# 定义插入数据的SQL语句
sql = "INSERT INTO txzp(RecruitPostName, LocationName, LastUpdateTime, CategoryName, Requirement, Responsibility) VALUES(%s,%s,%s,%s,%s,%s)"
params = [
item.get('RecruitPostName'),
item.get('LocationName'),
item.get('LastUpdateTime'),
item.get('CategoryName'),
item.get('Requirement'),
item.get('Responsibility'),
]
self.cursor.execute(sql, params)
self.conn.commit()
方法定义
1
def process_item(self, item, spider):
  • process_item:这是Scrapy框架中Pipeline对象的一个核心方法,用于处理从爬虫传递过来的每个item。
  • self:指向当前类(TxzpPipeline)的实例。
  • item:这是一个从爬虫传递到Pipeline的数据项。它通常是一个类似字典的对象,包含了爬虫提取的数据。
  • spider:引用当前的爬虫实例,允许访问爬虫特定的功能或数据。
SQL 语句的定义
1
sql = "INSERT INTO txzp(RecruitPostName, LocationName, LastUpdateTime, CategoryName, Requirement, Responsibility) VALUES(%s,%s,%s,%s,%s,%s)"
  • 这行代码定义了一个SQL插入语句,用于将数据插入到名为txzp的MySQL表中。
  • INSERT INTO txzp:指定要插入数据的表名。
  • (RecruitPostName, LocationName, LastUpdateTime, CategoryName, Requirement, Responsibility):这些是表中的列名,将为这些列插入数据。
  • VALUES(%s,%s,%s,%s,%s,%s):这是参数化的查询部分,%s是占位符,用于在执行时插入实际的数据值。
准备数据
1
2
3
4
5
6
7
8
params = [
item.get('RecruitPostName'),
item.get('LocationName'),
item.get('LastUpdateTime'),
item.get('CategoryName'),
item.get('Requirement'),
item.get('Responsibility'),
]
  • 这部分代码创建了一个列表params,包含了要插入到数据库中的数据。
  • item.get('RecruitPostName')等语句从item对象中提取相应的数据。get方法用于安全地访问字典键值,即使键不存在也不会引发错误。
执行SQL语句
1
self.cursor.execute(sql, params)
  • 使用之前创建的数据库游标self.cursor来执行SQL语句。
  • execute方法执行前面定义的SQL语句,并用params列表中的值替换占位符%s
  • 这种参数化查询方法可以有效防止SQL注入攻击,比直接将字符串拼接到SQL语句更安全。
提交事务
1
self.conn.commit()
  • 调用self.conn.commit()来提交事务,确保更改被保存到数据库。
  • 在数据库操作中,commit是一个重要步骤,它使得执行的操作(如插入、更新、删除)成为永久性的。
  • 如果不调用commit,那么即使执行了SQL语句,数据也不会被实际保存到数据库中。

执行确认

执行测试文件tx_debug.py

连接成功后执行测试文件db_debug.py

随后连接Linux检查数据库是否存储数据即可

Scray Request 类

Scrapy的Request类是用于表示一个HTTP请求的基本类。以下是其构造函数的参数和一些特殊的meta键值的详细说明:

构造函数参数

  • url (str): 请求的URL。
  • callback (callable): 当请求成功时,Scrapy将调用此回调函数处理响应。
  • method (str): HTTP方法(如'GET', 'POST'等)。默认为'GET'
  • headers (dict): 自定义的请求头。
  • body (strunicode): 请求体内容。如果未提供,默认为空字符串。
  • cookies (dict[dict]): 请求时附带的cookies。
  • meta (dict): 包含此请求的额外信息,可以在不同的回调函数间共享。
  • encoding (str): 编码类型,默认为'utf-8'。用于URL编码和将body转换为bytes。
  • priority (int): 请求的优先级。数字越大优先级越高,默认为0。
  • dont_filter (bool): 如果设置为True,则不会对请求进行去重过滤。
  • errback (callable): 如果请求过程中发生错误,将调用此函数。
  • flags (list): 一组字符串标志,通常用于日志记录或调试。
  • cb_kwargs (dict): 传递给回调函数的关键字参数。
1
2
3
4
5
6
7
8
9
from scrapy.http  import  Request,FormRequest

"""
class Request(object_ref):

def __init__(self, url, callback=None, method='GET', headers=None, body=None,
cookies=None, meta=None, encoding='utf-8', priority=0,
dont_filter=False, errback=None, flags=None, cb_kwargs=None):
"""

Request.meta 的特殊键值

  • dont_redirect (bool): 不处理HTTP重定向。
  • dont_retry (bool): 不对失败的HTTP请求进行重试。
  • handle_httpstatus_list (list): 包含HTTP状态码的列表,这些状态码的响应将不被视为错误。
  • handle_httpstatus_all (bool): 处理所有HTTP状态码。
  • dont_merge_cookies (bool): 不合并此请求的cookies。
  • cookiejar (int/object): 指定用于此请求的cookie jar。
  • dont_cache (bool): 禁止对此请求使用HTTP缓存。
  • redirect_reasons (list): 跟踪重定向的原因。
  • redirect_urls (list): 存储请求过程中的所有重定向URL。
  • bindaddress (tuple): 指定用于出站连接的本地IP地址和端口。
  • dont_obey_robotstxt (bool): 不遵守robots.txt规则。
  • download_timeout (float): 设置下载超时时间。
  • download_maxsize (int): 设置允许下载的最大字节数。
  • download_latency (float): 设置下载延迟。
  • download_fail_on_dataloss (bool): 如果出现数据丢失,下载失败。
  • proxy (str): 设置此请求的代理服务器。
  • ftp_user (str): FTP请求的用户名。
  • ftp_password (str): FTP请求的密码。
  • referrer_policy (str): 设置引用策略。
  • max_retry_times (int): 设置请求最大重试次数。

FormRequest 类

Scrapy框架中的FormRequest类,它是Request类的一个子类,专门用于处理表单请求。

FormRequest是Scrapy用来发送数据到表单的特殊请求类型。它继承自基本的Request类,并添加了处理表单数据的特定功能。

构造函数参数

  • \*args\**kwargs:
    • 这些是Python中的标准参数,允许接收任意数量的位置参数(*args)和关键字参数(**kwargs)。这在继承时使得FormRequest可以接收Request类接受的所有参数。
  • formdata (dict 或 类似于列表的元组对):
    • 这是用于传递表单数据的关键字参数。如果提供了formdataFormRequest将以application/x-www-form-urlencoded格式编码这些数据并包含在请求体中。
    • 如果method未指定且formdata存在,则默认将请求方法设置为POST

方法逻辑

  1. 检查formdatamethod:
    • 如果提供了formdata且未指定method,则默认将method设置为POST
  2. 调用基类构造函数:
    • 使用super(FormRequest, self).__init__(*args, **kwargs)调用基类(Request)的构造函数,确保所有基本的初始化逻辑被执行。
  3. 处理formdata:
    • 如果提供了formdata,它将被转换为查询字符串。
    • 对于POST请求,这个查询字符串将被设置为请求体,同时设置Content-Type头为application/x-www-form-urlencoded
    • 对于非POST请求(例如GET),查询字符串将被附加到URL上。

特别说明

  • valid_form_methods 是一个类属性,定义了FormRequest支持的HTTP方法。默认为['GET', 'POST']
  • _urlencode 函数用于将表单数据转换为URL编码的字符串。
  • _set_body_set_url 是内部方法,用于设置请求体和更新请求URL。

FormRequest通常用于向网站提交表单,例如登录页面、搜索查询等。由于它自动处理表单数据的编码,因此相比于普通的Request,在处理表单请求时更加方便。

在使用scrapy发动POST请求的时候,常使用此方法,能较方便的发送请求.具体的使用,见登录github案例;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class FormRequest(Request):
valid_form_methods = ['GET', 'POST']

def __init__(self, *args, **kwargs):
formdata = kwargs.pop('formdata', None)
if formdata and kwargs.get('method') is None:
kwargs['method'] = 'POST'

super(FormRequest, self).__init__(*args, **kwargs)

if formdata:
items = formdata.items() if isinstance(formdata, dict) else formdata
querystr = _urlencode(items, self.encoding)
if self.method == 'POST':
self.headers.setdefault(b'Content-Type', b'application/x-www-form-urlencoded')
self._set_body(querystr)
else:
self._set_url(self.url + ('&' if '?' in self.url else '?') + querystr)

HTTP响应处理

当使用Scrapy或类似的网络爬虫框架时,处理HTTP响应是一个常见的任务。以下是响应对象的主要属性和方法:

  • url(字符串): 此响应的URL。
  • status(整数): 响应的HTTP状态码。默认为200。
  • headers(字典): 此响应的响应头。可以是单值或多值。
  • body(字节): 响应主体。为字节类型,可使用response.text以字符串形式访问。
  • flags(列表): 包含初始响应标志的列表。
  • request(Request对象): 表示生成此响应的请求。

属性和方法

  • url: 包含请求URL的字符串。只读。
  • method: 表示HTTP方法的字符串。
  • headers: 请求头的类似字典对象。
  • body: 包含请求正文的字符串。只读。
  • meta: 包含请求的任意元数据的字典。
  • copy(): 返回此请求的副本。
  • replace(): 返回一个具有更新字段的新请求。

Scrapy日志配置和使用

在Scrapy项目中,正确配置和使用日志是监控爬虫行为、调试和记录关键信息的重要方面。

日志文件配置

在Scrapy的settings.py文件中,可以配置以下日志相关的设置:

  • LOG_FILE: 设置日志输出文件的路径。如果设置为None,日志将输出到控制台。
  • LOG_ENABLED: 控制是否启用日志记录,布尔值,默认为True
  • LOG_ENCODING: 设置日志文件的编码,默认为'utf-8'
  • LOG_LEVEL: 设置日志级别,默认为'DEBUG'。其他级别包括'INFO''WARNING''ERROR''CRITICAL'
  • LOG_FORMAT: 自定义日志的格式。格式选项包括:
    • %(levelno)s: 日志级别的数值。
    • %(levelname)s: 日志级别名称。
    • %(pathname)s: 执行程序的路径。
    • %(filename)s: 执行程序名。
    • %(funcName)s: 日志的当前函数。
    • %(lineno)d: 日志的当前行号。
    • %(asctime)s: 日志记录的时间。
    • %(thread)d: 线程ID。
    • %(threadName)s: 线程名称。
    • %(process)d: 进程ID。
    • %(message)s: 日志信息。
    • %(name)s: 日志记录器的名称。
  • LOG_DATEFORMAT: 设置日志的日期格式。
  • LOG_STDOUT: 布尔值,控制是否将标准输出(stdout)重定向到日志,通常设为False
  • LOG_SHORT_NAMES: 布尔值,设置为True时,日志记录器将使用短名称。

Python日志模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import logging

# 创建logger对象
logger = logging.getLogger('hello')

# 创建日志处理器:控制台和文件
streamH = logging.StreamHandler()
fileH = logging.FileHandler('test_log.txt')

# 设置日志格式
formatter = logging.Formatter('时间:%(asctime)s -- 日志级别:%(levelname)s -- 报错信息:%(message)s')

# 将格式应用到日志处理器
streamH.setFormatter(formatter)
fileH.setFormatter(formatter)

# 将处理器添加到logger
logger.addHandler(streamH)
logger.addHandler(fileH)

if __name__ == '__main__':
logger.error("我真的知道错误")
  • 在这个示例中,创建了一个名为hello的日志记录器。
  • 两个处理器(StreamHandlerFileHandler)分别用于输出日志到控制台和文件。
  • 使用Formatter设置了日志的格式。
  • 日志记录器通过添加这些处理器来启用日志记录。

项目中的常见设置

1
2
LOG_FILE = 'logfile_name.log'
LOG_LEVEL = 'INFO'
  • logger: Scrapy在每个Spider实例中提供了一个可用的logger实例,用于记录日志。

注意事项

  • 在生产环境中,通常将日志级别设置为INFOWARNING,以减少日志文件的大小。
  • 保证日志记录的详细程度与应用程序的需求相符,同时避免记录过于敏感的信息,如用户凭据等。
  • 日志文件的管理(如归档和清理)也是重要的,以避免日志文件占用过多磁盘空间。

GitHub登录过程分析与实现

登录分析

首先前往GitHub https://github.com/login 进行登录操作,检查登录的过程提交的数据

可以发现其在登录的过程中进行了POST请求,并提交表单到 https://github.com/session。关键在于准确地捕获并发送所有必要的表单参数。

检查其Payload可以查看到其参数和对应的值

由于密码和账户组合有三种情况,因此通过分别保持密码和账户相同分别再进行两次请求,并查看它们的值

比较三者的不同可以发现其是authenticity_tokenloginpasswordrequired_field_####timestamptimestamp_secret,因此需要在后面进行构造

登录参数

因此登录GitHub时,提交的表单数据不仅包含用户名和密码,还包括一些隐藏字段,如authenticity_tokentimestamptimestamp_secret。这些字段可能是为了安全性(如防止CSRF攻击)而设置的。

一个典型的登录请求包含以下参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
form_data = {
"commit": "Sign in",
"authenticity_token": authenticity_token,
"login": ACCOUNT, # 用户名
"password": PASSWORD, # 密码
"webauthn-support": "supported",
"webauthn-iuvpaa-support": "unsupported",
"return_to": "",
"required_field_####": "",
"timestamp": timestamp,
"timestamp_secret": timestamp_secret,
}

form_data 数据来源

这些数据大部分可以在访问 https://github.com/login 页面时从页面HTML中捕获。特别是authenticity_tokenrequired_field_timestamptimestamp_secret是动态生成的,因此每次登录前都需要先访问登录页面来获取这些数据。

请求流程

请求流程

  1. 访问 https://github.com/login 以获取登录所需的参数。
  2. https://github.com/session 提交POST请求,携带用户名、密码及其他必需数据。

Scrapy实现

编辑爬虫文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import scrapy
from git.spiders import USER

# from git.spiders import USER

class GithubLoginSpider(scrapy.Spider):
name = "github_login"
allowed_domains = ["github.com"]
start_urls = ["https://github.com/login"]

def parse(self, response):
authenticity_token = response.xpath('//input[@name="authenticity_token"]/@value').get()
required_field = response.xpath('//input[@type="text" and @hidden="hidden" ]/@name').get()
timestamp = response.xpath('//input[@name="timestamp"]/@value').get()
timestamp_secret = response.xpath('//input[@name="timestamp_secret"]/@value').get()

form_data = {
"commit": "Sign in",
'authenticity_token': authenticity_token,
"login": USER.LOGIN,
"password": USER.PASSWORD,
"webauthn-conditional": "undefined",
"javascript-support": "true",
"webauthn-support": "supported",
"webauthn-iuvpaa-support": "unsupported",
"return_to": "https://github.com/login",
'allow_signup': '',
'client_id': '',
'integration': '',
required_field: '',
"timestamp": timestamp,
"timestamp_secret": timestamp_secret,
}

yield scrapy.FormRequest(url="https://github.com/session", formdata=form_data, callback=self.login)

def login(self, response):
print(response)
if 'CosmicTrace' in response.text:
print("已成功拿到值")
else:
print('失敗了')
print(response)
代码解析
1
2
3
4
class GithubLoginSpider(scrapy.Spider):
name = "github_login"
allowed_domains = ["github.com"]
start_urls = ["https://github.com/login"]
  • 定义了一个名为GithubLoginSpider的Scrapy爬虫类。
  • name属性设置为"github_login",是爬虫的唯一标识。
  • allowed_domains列表限制了爬虫只能爬取"github.com"域名下的页面。
  • start_urls包含了爬虫开始爬取的URL,这里是GitHub的登录页面。
1
2
3
4
5
def parse(self, response):
authenticity_token = response.xpath('//input[@name="authenticity_token"]/@value').get()
required_field = response.xpath('//input[@type="text" and @hidden="hidden" ]/@name').get()
timestamp = response.xpath('//input[@name="timestamp"]/@value').get()
timestamp_secret = response.xpath('//input[@name="timestamp_secret"]/@value').get()
  • parse是Scrapy爬虫的默认回调方法,爬虫向一个URL发出请求时,获得的响应会自动传递给这个方法。在这个例子中,response 对象包含了对 start_urls 中URL的HTTP响应。
  • 这几行代码使用XPath从登录页面的HTML中提取了authenticity_tokenrequired_fieldtimestamptimestamp_secret。这些字段通常用于防止跨站请求伪造(CSRF)攻击。
    • response.xpath('//input[@name="authenticity_token"]/@value').get()
      • //input[@name="authenticity_token"]:这部分的XPath查找文档中所有<input>元素,其中name属性等于"authenticity_token"//表示在整个文档中查找,而不仅限于某个特定部分。
      • /@value:这部分表示从找到的<input>元素中提取value属性。在HTML中,<input>标签的value属性通常用来存储输入字段的值。
      • .get():这是Scrapy的Selector对象的方法,用于提取XPath选择器的第一个匹配结果。如果没有匹配的元素,它将返回None
    • response.xpath('//input[@type="text" and @hidden="hidden"]/@name').get()
      • //input[@type="text" and @hidden="hidden"]:这个XPath查找所有<input>元素,它们的type属性为"text"且同时拥有hidden="hidden"属性。这通常是隐藏的表单字段,对用户不可见,但对于表单提交可能是必需的。
      • /@name:这表示提取这些<input>元素的name属性。
      • .get():同样,这是用来获取第一个匹配结果的Scrapy方法。
    • response.xpath('//input[@name="timestamp"]/@value').get()
      • //input[@name="timestamp"]:这个XPath寻找所有<input>元素,其name属性为"timestamp"。这通常用于跟踪表单的创建或提交时间。
      • /@value:提取这些<input>元素的value属性。
      • .get():获取第一个匹配结果。
    • response.xpath('//input[@name="timestamp_secret"]/@value').get()
      • //input[@name="timestamp_secret"]:查找所有<input>元素,其name属性为"timestamp_secret"。这个值可能是与timestamp相关的加密或哈希值。
      • /@value:提取value属性。
      • .get():获取第一个匹配结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
form_data = {
"commit": "Sign in",
'authenticity_token': authenticity_token,
"login": USER.LOGIN,
"password": USER.PASSWORD,
"webauthn-conditional": "undefined",
"javascript-support": "true",
"webauthn-support": "supported",
"webauthn-iuvpaa-support": "unsupported",
"return_to": "https://github.com/login",
'allow_signup': '',
'client_id': '',
'integration': '',
required_field: '',
"timestamp": timestamp,
"timestamp_secret": timestamp_secret,
}
  • commit: 表单提交按钮的值,通常在登录表单中可以找到。
  • authenticity_token: 一个安全令牌,用于防止CSRF攻击,从登录页面的HTML中提取。
  • login: GitHub的用户名,USER.LOGIN应该替换为实际的用户名。
  • password: GitHub的密码,USER.PASSWORD应该替换为实际的密码。
  • webauthn-conditional, javascript-support, webauthn-support, webauthn-iuvpaa-support: 这些字段可能与GitHub的特定前端逻辑相关,例如Web认证和JavaScript支持。
  • return_to: 登录后应重定向到的URL。
  • allow_signup, client_id, integration: 这些可能是额外的表单字段,用于GitHub的内部跟踪或逻辑。
  • required_field: 之前从页面提取的隐藏字段,其确切意图可能是内部验证。
  • timestamptimestamp_secret: 与表单提交时效性和安全性相关的字段。
1
yield scrapy.FormRequest(url="https://github.com/session", formdata=form_data, callback=self.login)
  • 这行代码创建了一个Scrapy的FormRequest对象,用于向GitHub的https://github.com/session URL发送一个POST请求。
  • formdata=form_data 指定了要发送的表单数据。
  • callback=self.login 指定了Scrapy在收到响应后应调用的方法。self.login方法将处理登录请求的响应。
1
2
3
4
5
6
7
def login(self, response):
if 'CosmicTrace' in response.text:
print(response)
print("已成功拿到值")
else:
print('失敗了')
print(response)
  • def login(self, response): - 这行定义了一个名为 login 的方法。由于存在 self 参数,可以判断这个方法属于某个类,并且这个方法接受一个参数 response
  • if 'CosmicTrace' in response.text: - 这行代码检查 response.text 中是否包含字符串 ‘CosmicTrace’。如果包含,意味着满足了某种成功的条件。
  • print("已成功拿到值") - 如果在 response.text 中找到了字符串,就打印出 “已成功拿到值”,表示操作成功。
  • else: - 如果在 response.text 中没有找到字符串 ‘CosmicTrace’,则执行这部分代码。
  • print('失敗了') - 如果没有找到字符串,就打印出 ‘失敗了’,即“失败了”。
  • print(response) - 这行代码会在执行完if-else条件后打印整个 response 对象。这对于调试或记录日志很有用,可以查看请求的完整响应。
测试文件

编辑调试文件

1
2
3
4
from scrapy.cmdline import execute


execute(['scrapy', 'crawl', 'github_login'])
Settings 文件

编辑配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# Scrapy settings for git project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
# https://docs.scrapy.org/en/latest/topics/settings.html
# https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html

BOT_NAME = "git"

SPIDER_MODULES = ["git.spiders"]
NEWSPIDER_MODULE = "git.spiders"


# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = "git (+http://www.yourdomain.com)"

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

# Configure maximum concurrent requests performed by Scrapy (default: 16)
#CONCURRENT_REQUESTS = 32

# Configure a delay for requests for the same website (default: 0)
# See https://docs.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 3
# The download delay setting will honor only one of:
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
#CONCURRENT_REQUESTS_PER_IP = 16

# Disable cookies (enabled by default)
#COOKIES_ENABLED = False

# Disable Telnet Console (enabled by default)
#TELNETCONSOLE_ENABLED = False

# Override the default request headers:
DEFAULT_REQUEST_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en",
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
}

# Enable or disable spider middlewares
# See https://docs.scrapy.org/en/latest/topics/spider-middleware.html
#SPIDER_MIDDLEWARES = {
# "git.middlewares.GitSpiderMiddleware": 543,
#}

# Enable or disable downloader middlewares
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html
#DOWNLOADER_MIDDLEWARES = {
# "git.middlewares.GitDownloaderMiddleware": 543,
#}

# Enable or disable extensions
# See https://docs.scrapy.org/en/latest/topics/extensions.html
#EXTENSIONS = {
# "scrapy.extensions.telnet.TelnetConsole": None,
#}

# Configure item pipelines
# See https://docs.scrapy.org/en/latest/topics/item-pipeline.html
#ITEM_PIPELINES = {
# "git.pipelines.GitPipeline": 300,
#}

# Enable and configure the AutoThrottle extension (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/autothrottle.html
#AUTOTHROTTLE_ENABLED = True
# The initial download delay
#AUTOTHROTTLE_START_DELAY = 5
# The maximum download delay to be set in case of high latencies
#AUTOTHROTTLE_MAX_DELAY = 60
# The average number of requests Scrapy should be sending in parallel to
# each remote server
#AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
# Enable showing throttling stats for every response received:
#AUTOTHROTTLE_DEBUG = False

# Enable and configure HTTP caching (disabled by default)
# See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings
#HTTPCACHE_ENABLED = True
#HTTPCACHE_EXPIRATION_SECS = 0
#HTTPCACHE_DIR = "httpcache"
#HTTPCACHE_IGNORE_HTTP_CODES = []
#HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage"

# Set settings whose default value is deprecated to a future-proof value
REQUEST_FINGERPRINTER_IMPLEMENTATION = "2.7"
TWISTED_REACTOR = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"
FEED_EXPORT_ENCODING = "utf-8"
测试执行

调试测试文件,断点在login方法,发现其返回200,输出响应的TXT格式,复制其内容保存为HTML文件并打开

可以发现其成功显示出登录后的界面

也可以通过判断可以发现HTML是否含有账号名来判断调试是否成功

Scrapy下载中间件(Downloader Middleware)

下载中间件是Scrapy的核心组件之一,它提供了一个灵活的方式来自定义请求和响应的处理过程。

基本概念

  • 作用:下载中间件用于拦截并处理Scrapy发出的所有HTTP请求和响应。它们在Scrapy的请求/响应处理过程中提供了多个钩子(hooks)点。
  • 功能:可以用于修改请求和响应、处理重定向、重试失败的请求、设置代理、处理cookies等。
  • 实现:它是通过实现特定的方法的Python类来定义的。

内置中间件

  • Scrapy自带了多种下载中间件,这些中间件提供了对不同方面的处理支持。

  • 可以通过运行命令 scrapy settings --get=DOWNLOADER_MIDDLEWARES_BASE 查看Scrapy自带的所有下载中间件及其优先级。

    1
    {"scrapy.downloadermiddlewares.robotstxt.RobotsTxtMiddleware": 100, "scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware": 300, "scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware": 350, "scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware": 400, "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": 500, "scrapy.downloadermiddlewares.retry.RetryMiddleware": 550, "scrapy.downloadermiddlewares.ajaxcrawl.AjaxCrawlMiddleware": 560, "scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware": 580, "scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware": 590, "scrapy.downloadermiddlewares.redirect.RedirectMiddleware": 600, "scrapy.downloadermiddlewares.cookies.CookiesMiddleware": 700, "scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware": 750, "scrapy.downloadermiddlewares.stats.DownloaderStats": 850, "scrapy.downloadermiddlewares.httpcache.HttpCacheMiddleware": 900}
  • 这些内置中间件包括对robots.txt的处理、HTTP代理支持、Cookies处理等。

  • 用户可以通过在DOWNLOADER_MIDDLEWARES设置中添加自定义的中间件来扩展Scrapy的功能。

  • 设置是一个字典,键是中间件类的路径,值是中间件的顺序(0-1000之间的整数)。数字越小,优先级越高,即越接近Scrapy引擎

    1
    2
    3
    DOWNLOADER_MIDDLEWARES = {
    'myproject.middlewares.CustomMiddleware': 543,
    }

详见官方文档

https://docs.scrapy.org/en/latest/topics/downloader-middleware.html

下载中间件实例

  1. RobotsTxtMiddleware
  • 功能描述: 此中间件用于处理网站的robots.txt规则。它会自动解析并遵守目标网站的robots.txt文件,从而限制爬虫的行为以符合网站的爬取政策。
  • 配置方法: 默认情况下,Scrapy会遵守robots.txt规则。可以通过在项目的settings.py中设置ROBOTSTXT_OBEY = False来禁用此功能。
  1. HttpAuthMiddleware
  • 功能描述: 管理HTTP认证的中间件,用于处理那些需要HTTP认证(如基本认证、摘要认证)的网站。
  • 配置方法: 可以通过在Scrapy的Request对象中指定http_userhttp_pass属性来启用HTTP认证。
  1. DownloadTimeoutMiddleware
  • 功能描述: 设置下载请求的超时时间。如果请求在指定时间内未得到响应,它将被视为失败,并且可以被重试中间件重新处理。
  • 配置方法: 可以通过设置DOWNLOAD_TIMEOUT来调整全局超时时间。
  1. DefaultHeadersMiddleware
  • 功能描述: 为所有的Scrapy请求设置默认HTTP头部。这对于为每个请求添加或覆盖特定的HTTP头(如Accept-Language)非常有用。
  • 配置方法: 在settings.py文件中,使用DEFAULT_REQUEST_HEADERS设置来定义默认的HTTP头部。
  1. UserAgentMiddleware
  • 功能描述: 此中间件允许为每个请求随机或固定地设置User-Agent。User-Agent通常被网站用来识别访问者使用的浏览器和操作系统。
  • 配置方法: 可以通过在settings.py中设置USER_AGENT或者使用自定义的User-Agent提供器来改变User-Agent。
  1. RetryMiddleware
  • 功能描述: 处理失败的HTTP请求并尝试重新发送。这对于处理暂时的网络问题或服务器错误非常有用。
  • 配置方法: 默认情况下,Scrapy会重试失败的请求。可以在settings.py中修改RETRY_TIMES来调整重试次数。
  1. AjaxCrawlMiddleware
  • 功能描述: 用于处理JavaScript生成的页面。这使得Scrapy能够爬取那些需要执行JavaScript代码才能显示完整内容的页面。
  • 配置方法: 默认不启用,需要在settings.py中显式启用。
  1. MetaRefreshMiddleware
  • 功能描述: 自动处理页面的meta刷新标签。有些网页可能会使用meta标签来自动刷新或重定向到另一个页面。
  • 配置方法: 默认启用。可以通过在settings.py中设置METAREFRESH_ENABLED = False来禁用它。
  1. HttpCompressionMiddleware
  • 功能描述: 此中间件自动处理压缩的HTTP响应,例如gzip或deflate格式的内容。
  • 配置方法: 默认启用,无需特别配置。
  1. RedirectMiddleware
  • 功能描述: 自动处理HTTP重定向。对于301和302类型的重定向,此中间件会自动跟随重定向链接。
  • 配置方法: 默认启用,可以通过REDIRECT_ENABLED设置来禁用。
  1. CookiesMiddleware
  • 功能描述: 管理Cookies。对于需要管理多个会话或跟踪用户会话的爬虫来说,这个中间件非常有用。
  • 配置方法: 默认启用,可以通过COOKIES_ENABLED = False来禁用。
  1. HttpProxyMiddleware
  • 功能描述: 处理HTTP代理。通过使用代理,爬虫可以从不同的IP地址发送请求,这有助于绕过IP封锁或进行匿名抓取。
  • 配置方法: 可以通过在请求的meta中设置proxy键来启用代理。
  1. DownloaderStats
  • 功能描述: 收集下载统计信息。这个中间件为每个响应或异常收集统计数据,帮助分析和优化爬虫性能。
  • 配置方法: 默认启用,一般无需更改配置。
  1. HttpCacheMiddleware
  • 功能描述: 提供对HTTP缓存的支持。此中间件能够缓存请求的响应,以便下次请求相同资源时快速获取。
  • 配置方法: 默认不启用,可以通过在settings.py中设置HTTPCACHE_ENABLED = True来启用。

下载中间件API

  • 每个下载中间件可以实现以下一个或多个方法:
    • process_request(request, spider): 在发送请求之前调用。可以返回None、一个Response对象、一个Request对象或抛出一个异常。
    • process_response(request, response, spider): 在接收到响应后调用。可以返回Response对象或抛出异常。
    • process_exception(request, exception, spider): 当下载处理过程中发生异常时调用。
    • from_crawler(cls, crawler): 类方法,用于访问Scrapy的核心组件和API,以及创建中间件实例。

返回值的重要性

  • 每个方法的返回值都非常重要,它决定了请求或响应接下来的处理流程。
    • 如果process_request返回非None值,Scrapy将不会继续处理该请求,而是立即调用相应的process_response方法。
    • 如果process_response返回一个新的请求(Request对象),Scrapy将停止调用其他中间件的process_response方法,转而处理这个新的请求。
    • process_exception中返回一个新的请求对象将同样导致Scrapy停止调用其他中间件的process_exception方法,并处理这个新的请求。

自定义中间件

UA代理池中间件

在Scrapy项目中,使用自定义中间件来实现用户代理池(User-Agent Pool)是一种常见的做法,用于避免被目标网站识别并可能被阻止。

自定义的用户代理池中间件允许每个请求随机使用不同的用户代理(User-Agent),从而减少被目标网站识别为爬虫的风险。

1. 在 settings.py 中定义用户代理列表

首先,在Scrapy项目的settings.py文件中定义一个用户代理列表:

settings文件 user_agent_list

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#user_agent
USER_AGENT_LIST = [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
"Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
"(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
"(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
"(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
"(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
"Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
"(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
"(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
]

这个列表包含了多个不同的用户代理字符串,用于在发起请求时模拟不同的浏览器。

2. 在 middlewares.py 中实现用户代理中间件

middlewares.py文件中实现自定义的中间件,用于在每个请求中随机选择一个用户代理:

1
2
3
4
5
6
7
8
9
10
import random
from scrapy.conf import settings

class RandomUserAgentMiddleware(object):
def process_request(self, request, spider):
# 随机选择一个用户代理
user_agent = random.choice(settings.get('USER_AGENT_LIST'))
if user_agent:
# 设置请求的User-Agent
request.headers.setdefault('User-Agent', user_agent)
  • RandomUserAgentMiddleware 类定义了一个process_request方法,该方法在每个请求发送之前被调用。
  • 方法中,随机从USER_AGENT_LIST中选取一个用户代理,并将其设置为该请求的User-Agent

信息

也有另一种方法是直接从 settings 模块导入

1
2
3
4
5
6
7
8
9
10
from .settings import USER_AGENT_LIST
import random

class UAMiddleware:
def process_request(self, request, spider):
request.headers['User-Agent'] = random.choice(USER_AGENT_LIST)
return None

def process_response(self, request, response, spider):
return response
  • 这种方法直接从settings.py文件中导入user_agent_list
  • 优点是直接和简洁,尤其是在用户代理列表只在这个中间件中使用时。
  • 缺点是它降低了配置的灵活性。如果想在不同的环境(例如开发环境和生产环境)中使用不同的用户代理列表,或者希望能够通过命令行参数动态覆盖这些设置,这种方法可能不太适用。
  • 之前的方法使用Scrapy的内置settings模块来访问项目设置(在settings.py文件中定义的设置)。
  • 优点是它利用了Scrapy框架的设置管理机制,可以更容易地在整个项目中管理和维护这些设置。
  • 缺点是需要导入scrapy.conf.settings,这在某些情况下可能稍显冗余。
  • 如果倾向于使用Scrapy框架的标准特性,并希望在项目的不同组件间共享配置,那么第一种方法(使用scrapy.conf.settings)可能更适合。
  • 如果项目结构比较简单,或者只在一个地方使用这个用户代理列表,那么第二种方法(直接从settings.py导入)可能更直接有效。

3. 在 settings.py 中启用中间件

settings.py文件中,将自定义的中间件添加到DOWNLOADER_MIDDLEWARES设置中,以启用该中间件:

1
2
3
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.RandomUserAgentMiddleware': 400,
}
  • 替换'myproject.middlewares.RandomUserAgentMiddleware'为实际的中间件路径。
  • 数值400是中间件的优先级。可以根据需要调整这个值以控制中间件的执行顺序。

注意事项

  • 使用用户代理池可以帮助模拟常规用户的浏览行为,但应注意合理使用以避免对目标网站造成不必要的负担。
  • 请确保遵守目标网站的爬虫政策和使用条款。
  • 用户代理字符串应尽量选择常见且更新的版本,以提高爬虫的隐蔽性。

IP代理池

在Scrapy项目中使用IP代理池是为了隐藏爬虫的真实IP地址,这有助于绕过目标网站的IP封锁或请求频率限制。

实现

IP代理池可以让Scrapy爬虫在每次请求时使用不同的IP地址,从而提高爬虫的匿名性和效率。

1. 在 settings.py 中定义IP代理池

在Scrapy项目的settings.py文件中,定义一个包含多个代理IP的列表:

(此处是示例,以下的代理基本无法使用的,同时不建议去找免费的代理,不安全)

1
2
3
4
5
6
7
8
9
10
# IP代理池
IPPOOL = [
{"ipaddr": "61.129.70.131:8080"},
{"ipaddr": "61.152.81.193:9100"},
{"ipaddr": "120.204.85.29:3128"},
{"ipaddr": "219.228.126.86:8123"},
{"ipaddr": "61.152.81.193:9100"},
{"ipaddr": "218.82.33.225:53853"},
{"ipaddr": "223.167.190.17:42789"}
]

这个列表包含了多个字典,每个字典代表一个代理服务器,其中ipaddr键的值是代理服务器的IP地址和端口。

2. 在 middlewares.py 中实现IP代理中间件

middlewares.py文件中,实现一个中间件来随机使用IP代理池中的一个代理:

1
2
3
4
5
6
7
8
9
import random
from scrapy.conf import settings

class ProxyMiddleware(object):
def process_request(self, request, spider):
# 从IP池中随机选择一个IP
thisip = random.choice(settings.get('IPPOOL'))
# 将选中的IP设置为请求的代理
request.meta["proxy"] = "http://" + thisip['ipaddr']
  • ProxyMiddleware 类定义了一个process_request方法,该方法在每个请求发送之前被调用。
  • 方法中,随机从IPPOOL中选取一个IP代理,并将其设置为该请求的代理。

信息

第一种方法是从预定义的IP代理池中随机选择一个代理,而另一种种方法是实时从一个API获取代理IP

1
2
3
4
5
6
7
8
import requests

class IPPMiddleware:
def process_request(self, request, spider):
url = 'https://api.hailiangip.com:8522/api/getIpEt?dataType=1&encryptParam=...(省略)'
res = requests.get(url)
data = "https://" + res.text
request.meta['proxy'] = data

选择哪种方法取决于具体需求和环境。如果需要更高的灵活性和更大的代理IP池,且不介意依赖外部服务和可能的额外成本,那么使用实时API获取代理可能是更好的选择。如果更注重稳定性和控制,且不希望增加额外的依赖和成本,那么使用预定义的代理池可能更适合。

3. 在 settings.py 中启用自定义中间件

settings.py文件中,将自定义的中间件添加到DOWNLOADER_MIDDLEWARES设置中,以启用该中间件:

1
2
3
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.ProxyMiddleware': 543,
}
  • 替换'myproject.middlewares.ProxyMiddleware'为实际的中间件路径。
  • 数值543是中间件的优先级。可以根据需要调整这个值以控制中间件的执行顺序。
注意事项
  • 请注意,示例中的代理IP可能无法使用。在实际应用中,建议使用可靠且安全的付费代理服务。
  • 免费代理可能不稳定,且有安全隐患,例如可能会被用于拦截或篡改数据。
  • 使用代理时,请确保遵守目标网站的爬虫政策和使用条款,合理使用以避免对目标网站造成不必要的负担。

Scrapy settings.py 配置详解

优先级

settings.py 文件是 Scrapy 项目的核心配置文件,用于定义爬虫的行为和项目的全局设置。

基础配置

项目名称:

  • BOT_NAME: 定义项目名称,通常用作日志记录的标识。

    1
    BOT_NAME = 'baidu'

爬虫模块路径:

  • SPIDER_MODULES: 指定包含 Scrapy 爬虫的模块。

  • NEWSPIDER_MODULE: 定义使用 genspider 命令创建新爬虫时的默认模块。

    1
    2
    SPIDER_MODULES = ['baidu.spiders']
    NEWSPIDER_MODULE = 'baidu.spiders'

用户代理 (User-Agent):

  • USER_AGENT: 定义爬虫默认使用的用户代理。

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

Robots.txt 协议:

  • ROBOTSTXT_OBEY: 设置是否遵守网站的 robots.txt 协议。

    1
    ROBOTSTXT_OBEY = False

Cookies 支持:

  • COOKIES_ENABLED: 设置是否启用 cookies。

    1
    COOKIES_ENABLED = True

Telnet 控制台:

  • TELNETCONSOLE_ENABLED: 设置是否启用 Telnet 控制台用于查看爬虫运行情况。

    1
    TELNETCONSOLE_ENABLED = True

默认请求头:

  • DEFAULT_REQUEST_HEADERS: 设置 Scrapy 发送 HTTP 请求时默认使用的请求头。

    1
    2
    3
    4
    DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    'Accept-Language': 'en',
    }

请求重试:

  • RETRY_ENABLED: 设置是否启用请求重试。

  • RETRY_TIMES: 设置请求重试的次数。

  • RETRY_HTTP_CODECS: 设置触发重试的 HTTP 状态码。

    1
    2
    3
    RETRY_ENABLED = True
    RETRY_TIMES = 3
    RETRY_HTTP_CODECS = [500, 502, 503, 504, 408]

并发与延迟

最大并发请求数:

  • CONCURRENT_REQUESTS: 设置下载器最大处理的并发请求数量。

    1
    CONCURRENT_REQUESTS = 32

每个域的最大并发请求数:

  • CONCURRENT_REQUESTS_PER_DOMAIN: 设置每个域名的最大并发请求数。

    1
    CONCURRENT_REQUESTS_PER_DOMAIN = 16

每个IP的最大并发请求数:

  • CONCURRENT_REQUESTS_PER_IP: 设置每个 IP 的最大并发请求数。如果设置,CONCURRENT_REQUESTS_PER_DOMAIN 将被忽略。

    1
    CONCURRENT_REQUESTS_PER_IP = 16

下载延迟:

  • DOWNLOAD_DELAY: 设置对同一网站的请求间隔秒数。

    1
    DOWNLOAD_DELAY = 3

智能限速 (AutoThrottle)

AutoThrottle 扩展:

  • 自动调整 Scrapy 到最佳爬取速度,减轻对目标站点的压力。
  • AUTOTHROTTLE_ENABLED: 开启 AutoThrottle。
  • AUTOTHROTTLE_START_DELAY: 初始下载延迟。
  • AUTOTHROTTLE_MAX_DELAY: 最大下载延迟。
  • AUTOTHROTTLE_TARGET_CONCURRENCY: 每秒并发请求数的目标值。
  • AUTOTHROTTLE_DEBUG: 开启调试模式,以观察 AutoThrottle 的行为。
1
2
3
4
5
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 5
AUTOTHROTTLE_MAX_DELAY = 60
AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0
AUTOTHROTTLE_DEBUG = False

中间件、Pipelines、扩展

  1. 启用或禁用 Spider 中间件:

    1
    2
    3
    SPIDER_MIDDLEWARES = {
    'baidu.middlewares.BaiduSpiderMiddleware': 543,
    }
  2. 启用或禁用下载器中间件:

    1
    2
    3
    DOWNLOADER_MIDDLEWARES = {
    'baidu.middlewares.MyCustomDownloaderMiddleware': 543,
    }
  3. 启用或禁用扩展:

    1
    2
    3
    EXTENSIONS = {
    'scrapy.extensions.telnet.TelnetConsole': None,
    }
  4. 配置 Item Pipelines:

    • ITEM_PIPELINES: 设置启用的 Item Pipeline。
    1
    2
    3
    ITEM_PIPELINES = {
    'baidu.pipelines.CustomPipeline': 300,
    }

Spider类下载图片

在Scrapy框架中,下载图片与下载文本数据确实有一些相似之处,但也有其特有的处理方式。Scrapy通过内置的支持使得下载图片变得相对简单。与处理文本数据不同,处理图片通常涉及处理二进制数据,并可能需要额外的中间件支持。

测试文件

1
2
3
4
from scrapy.cmdline import execute


execute(['scrapy', 'crawl', 'baidu_img'])

手动保存图片

Spider文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import scrapy
import re,os


class BaiduImgSpider(scrapy.Spider):
name = "baidu_img"
allowed_domains = []
start_urls = ["https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111210&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&oq=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&rsp=-1"]

num = 0
def parse(self, response):
re_data = re.findall('thumbURL":"(.*?)"', response.text)
for im in re_data:
yield scrapy.Request(im, callback=self.get_img)

def get_img(self, response):
img_data = response.body
# 直接保存图片
# 如果没有 imgspider 这个文件夹,mkdir创建
if not os.path.exists('imgspider'):
os.mkdir('imgspider')
filename = 'imgspider/{}.png'.format(self.num)
self.num += 1
with open(filename, 'wb') as f:
f.write(img_data)
代码解释
  • 定义一个继承自scrapy.Spider的爬虫类BaiduImgSpider
  • name:为爬虫指定一个唯一的名称。
  • allowed_domains:定义爬虫允许爬取的域名列表。这里为空,表示不对域名进行限制。
  • start_urls:包含一个起始URL的列表,该URL是百度图片搜索的结果页面。
  • parse:是爬虫的一个方法,处理响应并提取数据。
  • 使用正则表达式从页面源代码中提取所有图片的URL。正则表达式'thumbURL":"(.*?)"'用于匹配图片的URL。
  • 遍历所有提取到的图片URL。
  • 对每个图片URL,生成一个Scrapy请求,并将响应发送到get_img方法。
  • get_img:处理图片下载的方法。
  • response.body:获取响应的二进制数据,即图片内容。
  • 检查是否存在名为imgspider的目录,如果不存在,则创建该目录。用于保存下载的图片。
  • 构造图片文件的保存路径和文件名。这里使用self.num来为图片生成唯一的文件名,但需要注意self.num在爬虫类中初始化。
  • 以二进制写入模式打开文件,将图片数据写入文件。
注意事项
  • 该爬虫直接从百度图片的搜索结果页面提取图片URL,具体的URL模式可能会随着百度网站的更新而变化。
  • 使用正则表达式提取数据可能不如使用XPath或CSS选择器那样稳定,因为如果百度网页的结构发生变化,正则表达式可能需要更新。
  • 确保遵守百度图片的版权和使用条款,不要用于任何侵犯版权或违法的用途。

调试处理前配置Settings文件进行伪装,否则连接失败

Pipeline 保存图片

Items 文件

1
2
3
4
5
6
7
8
9
10
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class BaiduItem(scrapy.Item):
img_data = scrapy.Field()
  • 定义一个名为BaiduItem的Item类,该类用于在爬虫和Pipeline之间传递数据。
  • 它继承自scrapy.Item。在Scrapy中,Item是用来收集从网页提取的数据的简单容器。
  • BaiduItem类将被用来存储从百度图片搜索结果中提取的数据。
  • BaiduItem类中定义了一个字段img_data
  • scrapy.Field()是Scrapy用来定义Item字段的特殊容器,用于存储从网页中提取的数据。
  • 在这个例子中,img_data字段用于存储图片的二进制数据或图片的URL。
  • 创建了一个BaiduItem的实例,并将提取到的图片数据或URL填充到img_data字段。
  • 然后,可以将这个填充了数据的BaiduItem实例传递给Pipeline进行进一步的处理,例如保存图片到文件系统或数据库

Spider 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import scrapy
import re,os
from ..items import BaiduItem


class BaiduImgSpider(scrapy.Spider):
name = "baidu_img"
allowed_domains = []
start_urls = ["https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111210&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&oq=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&rsp=-1"]

def parse(self, response):
re_data = re.findall('thumbURL":"(.*?)"', response.text)
for im in re_data:
yield scrapy.Request(im, callback=self.get_img)

def get_img(self, response):
item = BaiduItem()
img_data = response.body
item['img_data'] = img_data

yield item
  • 定义一个名为BaiduImgSpider的爬虫类,指定爬虫名称、允许的域名(在这里为空)和起始URL。
  • parse是Scrapy爬虫处理响应的默认方法。使用正则表达式从响应中提取图片的URL。
  • 对每个找到的图片URL,发起一个新的Scrapy请求,并指定回调方法get_img来处理这些请求。
  • get_img方法处理图片下载的响应。创建一个BaiduItem实例,并将下载的图片数据(二进制格式)存储在img_data字段中。
  • 将包含图片数据的item提交给Pipeline进行进一步处理。

Pipelines 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from itemadapter import ItemAdapter
import os

class BaiduPipeline():
num = 0

def process_item(self, item, spider):
if not os.path.exists('imgpipeline'):
os.mkdir('imgpipeline')
filename = 'imgpipeline/{}.png'.format(self.num)
self.num += 1
with open(filename, 'wb') as f:
f.write(item.get('img_data'))
return item
  • 导入了所需的模块和类。ItemAdapter用于适配不同类型的Item对象,os模块用于处理文件和路径操作。
  • num是一个类变量,用于生成图片文件的名称。
  • process_item是Pipeline处理item的方法。
  • 检查名为imgpipeline的文件夹是否存在,如果不存在,则创建它。这个文件夹用于存储下载的图片。
  • 为下载的图片生成文件名,并将num递增以确保文件名的唯一性。
  • 以二进制写模式打开文件,并将图片数据写入文件。
  • 返回处理后的item。

settings.py中激活BaiduPipeline避免没有传值

注意事项

  • 这个爬虫和Pipeline的实现假定图片的URL可以直接从百度图片搜索结果的页面HTML中提取。如果百度更改其HTML结构或JavaScript动态加载机制,这个方法可能需要更新。
  • 当处理大量图片或大型网站时,请确保遵循robots.txt规则并尊重网站的版权和使用条款。

ImagesPipeline类下载图片

Scrapy提供了一个专门的ImagesPipeline类,用于方便地下载和处理图片。要正确使用这个类,需要按照以下步骤操作:

  1. 在Spider文件中提取图片URLs

    • 爬虫应该解析目标页面,提取图片的URLs,并将它们存储在item的一个字段中。
  2. 在Items文件中定义image_urls字段

    • 定义一个Scrapy Item,并包含一个名为image_urls的字段,用于存储待下载的图片URLs。
1
2
class BdImagePipeItem(scrapy.Item):
image_urls = scrapy.Field()
  1. 创建继承自ImagesPipeline的管道类
    • 创建一个新的Pipeline类,继承自ImagesPipeline。这个类可以被用来进一步自定义图片的下载和处理行为(如过滤、转换格式等)。
1
2
3
4
from scrapy.pipelines.images import ImagesPipeline

class BdImagePipeline(ImagesPipeline):
pass
  1. 在Settings文件中配置图片存储路径和Pipeline
    • 在项目的settings.py文件中,设置图片存储路径(IMAGES_STORE)和启用图片管道。
1
2
3
4
IMAGES_STORE = '/path/to/your/images/dir'
ITEM_PIPELINES = {
'yourproject.pipelines.BdImagePipeline': 300,
}
  1. 安装Pillow库

    • ImagesPipeline需要Pillow库来处理图片。确保安装了Pillow库(版本4.0或以上)。

1
pip install Pillow
  1. 媒体管道的特性和设置

    • Scrapy的媒体管道(包括ImagesPipeline)提供了一些有用的特性,例如避免重新下载最近下载的媒体、生成缩略图、检查图像尺寸等。

    • 除了IMAGES_STORE之外,还有许多其他设置可以用来定制媒体管道的行为,如IMAGES_EXPIRESIMAGES_THUMBSIMAGES_MIN_HEIGHTIMAGES_MIN_WIDTH等。

媒体管道的特性

Scrapy的媒体管道提供了强大的功能来处理下载的媒体文件,如图片和文件。特别是对于图像,Scrapy提供了额外的处理功能。

基本特性

  • 避免重复下载:媒体管道会跟踪最近下载的文件,避免重复下载相同的媒体内容。
  • 灵活的存储位置:支持多种存储方式,包括本地文件系统、Amazon S3、谷歌云存储等。

图像特有功能

  • 格式转换:下载的图片会被转换为通用的JPG格式,并确保图片模式为RGB。
  • 缩略图生成:可以自动生成指定尺寸的缩略图。
  • 尺寸过滤:通过设置,可以过滤掉低于指定宽度或高度的图片。

媒体管道的设置

为了使用媒体管道,需要在项目的settings.py文件中进行相应的配置。

启用媒体管道

1
ITEM_PIPELINES = {'scrapy.pipelines.images.ImagesPipeline': 1}
  • 这行代码启用了Scrapy的图像管道,并设置其优先级为1。

设置存储位置和字段

1
2
3
4
5
6
7
8
9
10
11
12
FILES_STORE='/path/to/valid/dir'	文件管道存放位置
IMAGES_STORE='/path/to/valid/dir' 图片管道存放位置
FILES_URLS_FIELD='field_name_for_your_files_urls' 自定义文件url字段
FILES_RESULT_FIELD='field_name_for_your_processed_files' 自定义结果字段
IMAGES_URLS_FIELD = 'field_name_for_your_images_urls' 自定义图片url字段
IMAGES_RESULT_FIELD = 'field_name_for_your_processed_images' 结果字段
FILES_EXPIRES = 90 文件过期时间 默认90
IMAGES_EXPIRES = 90 图片过期时间 默认90
IMAGES_THUMBS= {'small': (50, 50), 'big':(270, 270)} 缩略图尺寸
IMAGES_MIN_HEIGHT= 110 过滤最小高度
IMAGES_MIN_WIDTH= 110 过滤最小宽度
MEDIA_ALLOW_REDIRECTS= True 是否重定向,默认为False

Spider文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import scrapy
import re,os
from ..items import BaiduItem


class BaiduImgSpider(scrapy.Spider):
name = "baidu_img"
allowed_domains = []
start_urls = ["https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111210&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&oq=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&rsp=-1"]

def parse(self, response):
re_data = re.findall('thumbURL":"(.*?)"', response.text)

item = BaiduItem()
item['image_urls'] = re_data
yield item
  • 导入了Scrapy框架、正则表达式库和项目中定义的BaiduItem
  • 定义了一个名为BaiduImgSpider的爬虫类。
  • start_urls包含了开始爬取的URL(百度图片搜索结果)。
  • parse方法处理响应并提取图片URL。
  • 使用正则表达式从页面中提取图片的URL。
  • 创建一个BaiduItem实例,并将提取到的URL列表赋值给image_urls字段。
  • 使用yield语句返回这个item。

Items文件

1
2
3
4
5
6
7
8
9
10
# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy


class BaiduItem(scrapy.Item):
image_urls = scrapy.Field()
  • 定义了一个名为BaiduItem的Item类,包含一个image_urls字段。
  • image_urls用于存储待下载的图片URL列表。

Pipelines文件

1
2
3
4
5
6
7
8
9
10
11
12
# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://docs.scrapy.org/en/latest/topics/item-pipeline.html


# useful for handling different item types with a single interface
from scrapy.pipelines.images import ImagesPipeline


class BaiduPipeline(ImagesPipeline):
pass
  • 导入了Scrapy的ImagesPipeline
  • 定义了一个名为BaiduPipeline的类,它继承自ImagesPipeline
  • 目前这个类没有进行任何自定义操作,直接继承了ImagesPipeline的全部功能。

Settings文件

添加图片管道存放位置IMAGES_STORE='imgStore'

添加缩略图尺寸设置:

1
2
3
4
IMAGES_THUMBS= {
'small': (60, 60),
'large':(300, 300)
}

ImagesPipeline类方法重写 (改名与翻页)

Spider文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import scrapy
import re,os
from ..items import BaiduItem


class BaiduImgSpider(scrapy.Spider):
name = "baidu_img"
allowed_domains = []
start_urls = ["https://image.baidu.com/search/index?tn=baiduimage&ipn=r&ct=201326592&cl=2&lm=-1&st=-1&fm=index&fr=&hs=0&xthttps=111210&sf=1&fmq=&pv=&ic=0&nc=1&z=&se=1&showtab=0&fb=0&width=&height=&face=0&istype=2&ie=utf-8&word=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&oq=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&rsp=-1"]

page_url ='https://image.baidu.com/search/acjson?tn=resultjson_com&logid=7201144097221860430&ipn=rj&ct=201326592&is=&fp=result&fr=&word=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&queryWord=%E7%8E%8B%E8%80%85%E8%8D%A3%E8%80%80&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=&st=-1&z=&ic=0&hd=&latest=&copyright=&s=&se=&tab=&width=&height=&face=0&istype=2&qc=&nc=1&expermode=&nojc=&isAsync=&pn={}&rn=30&gsm=1e&1702544763785='

page_num = 1

def parse(self, response):
re_data = re.findall('thumbURL":"(.*?)"', response.text)

item = BaiduItem()
item['image_urls'] = re_data
yield item

if self.page_num == 4:
return
url = self.page_url.format(self.page_num * 30)
self.page_num += 1
yield scrapy.Request(url)
  • page_url定义了用于翻页的URL模板。
  • page_num用于追踪当前的翻页数。
  • parse方法用正则表达式从响应中提取图片URL,并将它们存入BaiduItemimage_urls字段。
  • 控制翻页,如果当前页面数达到4,则停止爬取。
  • 格式化page_url以获取下一页的URL,并更新page_num
  • 使用scrapy.Request生成新的请求来爬取下一页。

Pipelines文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from itemadapter import ItemAdapter
import os
from .settings import IMAGES_STORE
from scrapy.pipelines.images import ImagesPipeline

class BaiduPipeline(ImagesPipeline):
pass
num = 0
def item_completed(self, results, item, info):
images_paths = [x.get('path') for ok, x in results if ok]
for path in images_paths:
os.rename(os.path.join(IMAGES_STORE,path),
os.path.join(IMAGES_STORE, str(self.num) + '.png')
)
self.num += 1
  • 导入所需的模块和类,包括Scrapy的内置ImagesPipeline

  • 定义了继承自ImagesPipelineBaiduPipeline类。

  • num用于生成重命名后的图片文件名。

  • 重写item_completed方法,该方法在item的图片被下载完成后调用。

  • 遍历下载的图片路径,并重命名每张图片,使用num作为新的文件名。

  • 标题: Scrapy
  • 作者: Yiuhang Chan
  • 创建于 : 2021-10-28 13:12:54
  • 更新于 : 2024-02-28 18:50:35
  • 链接: https://www.yiuhangblog.com/2021/10/28/20211128scrapy/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
此页目录
Scrapy