返回博客
指南
Mihai MaximLast updated on Mar 31, 20264 min read

使用 Scrapy 进行网页抓取:轻松上手

使用 Scrapy 进行网页抓取:轻松上手

使用 Scrapy 进行网页抓取

Scrapy 是一个功能强大的 Python 库,用于从网站中提取数据。它速度快、效率高且易于使用——相信我,我亲身体验过。无论你是数据科学家、开发者,还是喜欢玩转数据的人,Scrapy 都能为你提供帮助。最棒的是,它完全免费且开源。

Scrapy 项目自带的文件结构能帮助你整理代码和数据。这使得构建和维护网络爬虫变得更加轻松,因此如果你计划进行任何严肃的网络爬取工作,它绝对值得考虑。使用 Scrapy 进行网络爬取,就像在你踏上数据提取之旅时,身边有一位得力助手(尽管是虚拟的)相伴。

我们将要构建的内容

学习新技能时,阅读理论是一回事,实际操作又是另一回事。正因如此,我们决定在阅读本指南的同时,共同构建一个爬虫。这是亲身体验 Scrapy 网页爬取原理的最佳方式。

那么,我们具体要构建什么呢?我们将开发一个爬虫,用于从 Urban Dictionary 网站抓取单词释义。这是一个有趣的爬取目标,能让学习过程更加愉快。 我们的爬虫程序将非常简单——它将返回 Urban Dictionary 网站上各种单词的释义。我们将利用 Scrapy 内置的 HTML 文档选择和数据提取功能,提取出我们所需的释义。

那么,让我们开始吧!在下一节中,我们将介绍跟上本教程所需的先决条件。下一节见!

先决条件

在深入构建爬虫之前,您需要完成一些准备工作。本节将介绍如何安装 Scrapy 并为项目设置虚拟环境。Scrapy 文档建议在专用的虚拟环境中安装 Scrapy。这样做可以避免与系统包发生冲突。

我正在 Ubuntu 22.04.1 WSL(Windows 子系统 for Linux)上运行 Scrapy,因此我将为我的机器配置一个虚拟环境。

建议您先阅读“理解文件夹结构”一章,以全面了解我们将要使用的工具。此外,请浏览“Scrapy Shell”一章,这将使您的开发体验变得更加轻松。

设置 Python 虚拟环境

要在 Ubuntu 上为 Python 设置虚拟环境,可以使用 mkvirtualenv 命令。首先,请确保已安装 virtualenv 和 virtualenvwrapper:

$ sudo apt-get install virtualenv virtualenvwrapper

在 .bashrc 文件末尾添加以下内容:

export WORKON_HOME=$HOME/.virtualenvs

export PROJECT_HOME=$HOME/Deve

export VIRTUALENVWRAPPER_PYTHON='/usr/bin/python3'

source /usr/local/bin/virtualenvwrapper.sh

我使用 Vim 编辑了该文件,但你可以选择任何你喜欢的编辑器:

vim ~/.bashrc

// Use ctr + i to enter insert mode, use the down arrow to scroll to the bottom of the file.

// Paste the lines at the end of the file.

// Hit escape to exit insert mode, type wq and hit enter to save the changes and exit Vim.

然后使用 mkvirtualenv 创建一个新的虚拟环境:

$ mkvirtualenv scrapy_env

此时,终端命令行开头应显示 (scrapy_env) 字样。

要退出虚拟环境,请输入 $ deactivate

要返回 scrapy_env 虚拟环境,请输入 $ workon scrapy_env

安装 Scrapy

您可以使用 pip 包管理器安装 Scrapy:

$ pip install scrapy

这将安装 Scrapy 的最新版本。

您可以使用 scrapy startproject 命令创建一个新项目:

$ scrapy startproject myproject

这将初始化一个名为“myproject”的新 Scrapy 项目。该项目应包含默认的项目结构。

了解文件夹结构

这是默认的项目结构:

myproject

├── myproject

│ ├── __init__.py

│ ├── items.py

│ ├── middlewares.py

│ ├── pipelines.py

│ ├── settings.py

│ └── spiders

│ └── __init__.py

└── scrapy.cfg

items.py

items.py 是用于存储从网站提取数据的模型。该模型将用于存储您从网站提取的数据。

示例:

import scrapy

class Product(scrapy.Item):

    name = scrapy.Field()

    price = scrapy.Field()

    description = scrapy.Field()

在此我们定义了一个名为 Product 的 Item。它可被 Spider(参见 /spiders)用于存储产品的名称、价格和描述信息。

/spiders

/spiders 是一个包含蜘蛛(Spider)类的文件夹。在 Scrapy 中,蜘蛛(Spider)是定义网站抓取方式的类。

示例:

import scrapy

from myproject.items import Product

class MySpider(scrapy.Spider):

    name = 'myspider'

    start_urls = ['<example_website_url>']

    

    def parse(self, response):

        # Extract the data for each product

        for product_div in response.css('div.product'):

            product = Product()

            product['name'] = product_div.css('h3.name::text').get()

            product['price'] = product_div.css('span.price::text').get()

            product['description'] = product_div.css('p.description::text').get()

            yield product

该“Spider”遍历 start_urls 中的网址,通过 CSS 选择器提取页面上所有产品的名称、价格和描述,并将数据存储在 Product 项中(参见 items.py)。随后,它“yield”这些项,促使 Scrapy 将其传递给管道中的下一个组件(参见 pipelines.py)。

Yield 是 Python 中的一个关键字,它允许函数在不结束函数的情况下返回一个值。相反,它会生成该值并暂停函数的执行,直到被请求下一个值。

例如:

def count_up_to(max):

    count = 1

    while count <= max:

        yield count

        count += 1

for number in count_up_to(5):

    print(number)

// returns 1 2 3 4 5 (each on a new line)  

pipelines.py

管道负责处理由蜘蛛提取的项(参见 items.py 和 /spiders)。您可以利用它们清理 HTML、验证数据,并将数据导出为自定义格式或保存至数据库。

示例:

import pymongo

class MongoPipeline(object):

    def __init__(self):

        self.conn = pymongo.MongoClient('localhost', 27017)

        self.db = self.conn['mydatabase']

        self.product_collection = self.db['products']

        self.other_collection = self.db['other']

        

    def process_item(self, item, spider):

        if spider.name == 'product_spider':

            //insert item in the product_collection

        elif spider.name == 'other_spider':

            //insert item in the other_collection

        return item

我们创建了一个名为 MongoPipeline 的管道。它连接到两个 MongoDB 集合(product_collection 和 other_collection)。该管道从蜘蛛(参见 /spiders)接收项目(参见 items.py),并在 process_item 函数中对其进行处理。在此示例中,process_item 函数将项目添加到其指定的集合中。

settings.py

settings.py 存储了多种控制 Scrapy 项目行为的设置,例如应使用的管道、中间件和扩展,以及与项目如何处理请求和响应相关的设置。

例如,您可以使用它来设置管道的执行顺序(参见 pipelines.py):

ITEM_PIPELINES = {

    'myproject.pipelines.MongoPipeline': 300,

    'myproject.pipelines.JsonPipeline': 302,

}

// MongoPipeline will execute before JsonPipeline, because it has a lower order number(300)

或设置导出容器:

FEEDS = {

    'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

}

// this will save the scraped data to a items.json

scrapy.cfg

scrapy.cfg 是项目主要配置的配置文件。

[settings]

default = [name of the project].settings

[deploy]

#url = http://localhost:6800/

project = [name of the project]

middlewares.py

Scrapy 中有两种中间件:下载器中间件和蜘蛛中间件。

下载器中间件是可用于修改请求和响应、处理错误以及实现自定义下载逻辑的组件。它们位于蜘蛛和 Scrapy 下载器之间。

蜘蛛中间件是用于实现自定义处理逻辑的组件。它们位于引擎和蜘蛛之间。

Scrapy Shell

在开启实现 Urban Dictionary 爬虫这一激动人心的旅程之前,我们首先应该熟悉 Scrapy Shell。这个交互式控制台允许我们测试爬取逻辑并实时查看结果。它就像一个虚拟沙盒,让我们可以在将蜘蛛放上网络之前,在此进行尝试并调整我们的方法。相信我,从长远来看,这将为你节省大量时间并避免许多麻烦。 那么,让我们开始探索并熟悉 Scrapy Shell 吧。

打开 Shell

要打开 Scrapy Shell,首先需要在终端中导航至你的 Scrapy 项目目录。然后,只需运行以下命令:

scrapy shell

这将打开 Scrapy Shell,并显示一个提示符,供你输入和执行 Scrapy 命令。你还可以将 URL 作为参数传递给 shell 命令,直接抓取网页,如下所示:

scrapy shell <url>

例如:

scrapy shell https://www.urbandictionary.com/define.php?term=YOLO

将返回(在响应对象中)包含“YOLO”一词定义(来自《Urban Dictionary》)的网页 HTML 内容。

此外,进入 Shell 后,您还可以使用 fetch 命令来抓取网页。

fetch('https://www.urbandictionary.com/define.php?term=YOLO')

您还可以使用 nolog 参数启动 Shell,使其不显示日志:

scrapy shell --nolog

使用 Shell

在此示例中,我抓取了“https://www.urbandictionary.com/define.php?term=YOLO”这个 URL,并将 HTML 保存到了 test_output.html 文件中。

(scrapy_env) mihai@DESKTOP-0RN92KH:~/myproject$ scrapy shell --nolog

[s] Available Scrapy objects:

[s] scrapy scrapy module (contains scrapy.Request, scrapy.Selector, etc)

[s] crawler <scrapy.crawler.Crawler object at 0x7f1eef80f6a0>

[s] item {}

[s] settings <scrapy.settings.Settings object at 0x7f1eef80f4c0>

[s] Useful shortcuts:

[s] fetch(url[, redirect=True]) Fetch URL and update local objects (by default, redirects are followed)

[s] fetch(req) Fetch a scrapy.Request and update local objects

[s] shelp() Shell help (print this help)

[s] view(response) View response in a browser

>>> response // response is empty

>>> fetch('https://www.urbandictionary.com/define.php?term=YOLO')

>>> response

<200 https://www.urbandictionary.com/define.php?term=Yolo>

>>> with open('test_output.html', 'w') as f:

... f.write(response.text)

...

118260

现在让我们检查 test_output.html,并确定我们需要哪些选择器来提取 Urban Dictionary 爬虫所需的数据。

我们可以观察到:

  • 每个单词定义容器都带有“definition”类。
  • 单词的释义位于类名为“meaning”的 div 中。
  • 该词的示例位于类名为“example”的 div 中。
  • 关于文章作者和发布日期的信息位于类名为“contributor”的 div 中。

现在让我们在 Scrapy Shell 中测试一些选择器:

要获取每个定义容器的引用,我们可以使用 CSS 或 XPath 选择器:

您可以在此处了解更多关于 XPath 选择器的信息: https://www.webscrapingapi.com/the-ultimate-xpath-cheat-sheet

definitions = response.css('div.definition')

definitions = response.xpath('//div[contains(@class,"definition")]')

我们需要从每个定义容器中提取含义、示例和文章信息。让我们用第一个容器测试一些选择器:

>>> first_def = definitions[0]

>>> meaning = first_def.css('div.meaning').xpath(".//text()").extract()

>>> meaning

['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

>>> meaning = "".join(meaning)

>>> meaning

'Yolo means, 'You Only Live Once'.'

>>> example = first_def.css('div.example').xpath(".//text()").extract()

>>> example = "".join(example)

>>> example

'"Put your seatbelt on." Jessica said.\r"HAH, YOLO!" Replies Anna.\r(They then proceed to have a car crash. Long story short...Wear a seatbelt.)'

>>> post_data = first_def.css('div.contributor').xpath(".//text()").extract()

>>> post_data

['by ', 'Soy ugly', ' April 24, 2019']

通过使用 Scrapy Shell,我们能够快速找到一个符合需求的通用选择器。

definition.css('div.<meaning|example|contributor>').xpath(".//text()").extract()

// returns an array with all the text found inside the <meaning|example|contributor>

ex: ['Yolo ', 'means', ', '', 'You Only Live Once', ''.']

如需深入了解 Scrapy 选择器,请查阅文档。https://docs.scrapy.org/en/latest/topics/selectors.html

实现 Urban Dictionary 爬虫

干得漂亮!既然你已经掌握了 Scrapy Shell 的使用方法,并理解了 Scrapy 项目的内部运作机制,现在是时候深入实现我们的 Urban Dictionary 爬虫了。到目前为止,你应该已经充满信心,准备好承担从网络上提取所有那些搞笑(有时甚至令人质疑)的词条定义的任务了。那么,事不宜迟,让我们开始构建我们的爬虫吧!

定义 Item

首先,我们将实现一个 Item 类:(参见 items.py)

class UrbanDictionaryItem(scrapy.Item):

    meaning = scrapy.Field()

    author = scrapy.Field()

    date = scrapy.Field()

    example = scrapy.Field()

该结构将用于存储从 Spider 抓取的数据。

定义蜘蛛

以下是定义我们的蜘蛛的方式(参见 /spiders):

import scrapy

from ..items import UrbanDictionaryItem

class UrbanDictionarySpider(scrapy.Spider):

name = 'urban_dictionary'

start_urls = ['https://www.urbandictionary.com/define.php?term=Yolo']

def parse(self, response):

definitions = response.css('div.definition')

for definition in definitions:

item = UrbanDictionaryItem()

item['meaning'] = definition.css('div.meaning').xpath(".//text()").extract()

item['example'] = definition.css('div.example').xpath(".//text()").extract()

author = definition.css('div.contributor').xpath(".//text()").extract()

item['date'] = author[2]

item['author'] = author[1]

yield item

要运行 urban_dictionary 爬虫,请使用以下命令:

scrapy crawl urban_dictionary

// the results should appear in the console (most probably at the top of the logs)

创建管道

此时,数据尚未经过清理。

{'author': 'Soy ugly',

 'date': ' April 24, 2019',

 'example': ['“Put your ',

             'seatbelt',

             ' on.” Jessica said.\n',

             '“HAH, YOLO!” Replies Anna.\n',

             '(They then proceed to have a ',

             'car crash',

             '. ',

             'Long story short',

             '...Wear a seatbelt.)'],

 'meaning': ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.']}

我们需要将“example”和“meaning”字段修改为存储字符串,而非数组。为此,我们将编写一个管道(参见 pipelines.py),通过将单词拼接起来,将数组转换为字符串。

class SanitizePipeline:

    def process_item(self, item, spider):

        # Sanitize the 'meaning' field

        item['meaning'] = "".join(item['meaning'])

        # Sanitize the 'example' field

        item['example'] = "".join(item['example'])

       

        # Sanitize the 'date' field

        item['date'] = item['date'].strip() 

        return item

 //ex: ['Yolo ', 'means', ', ‘', 'You Only Live Once', '’.'] turns to 

       'Yolo means, ‘You Only Live Once’.'

启用管道

定义好管道后,我们需要启用它。如果不启用,我们的 UrbanDictionaryItem 对象将不会经过清理。为此,请将其添加到 settings.py(参见 settings.py)文件中:

 ITEM_PIPELINES = {

     'myproject.pipelines.SanitizePipeline': 1,

 }

顺便,您还可以指定一个输出文件,用于存放抓取的数据。

 FEEDS = {

     'items': {'uri': 'file:///tmp/items.json', 'format': 'json'},

 }

  // this will put the scraped data in an items.json file.

可选:实现代理中间件

网络爬取有时会让人头疼。我们经常遇到的一大问题是,许多网站需要通过 JavaScript 渲染才能完整显示内容。这对我们这些网络爬虫来说可能造成巨大困扰,因为我们的工具通常不具备像普通网页浏览器那样执行 JavaScript 的能力。这可能导致提取的数据不完整,或者更糟的是,因短时间内发出过多请求而被网站封禁 IP。

我们针对这一问题的解决方案是 WebScrapingApi。通过我们的服务,您只需向 API 发起请求,它便会为您处理所有繁重的工作。它将执行 JavaScript、轮换代理,甚至处理验证码,确保您能够轻松抓取那些最难搞的网站。

代理中间件会将 Scrapy 发出的每个 fetch 请求转发至代理服务器。随后,代理服务器将代我们发出请求并返回结果。

首先,我们在 middlewares.py 文件中定义一个 ProxyMiddleware 类。


import base64

class ProxyMiddleware:

    def process_request(self, request, spider):

        # Set the proxy for the request

        request.meta['proxy'] = "http://proxy.webscrapingapi.com:80"

        request.meta['verify'] = False

        # Set the proxy authentication for the request

        proxy_user_pass =  "webscrapingapi.proxy_type=residential.render_js=1:<API_KEY>"

        encoded_user_pass = base64.b64encode(proxy_user_pass.encode()).decode()

        request.headers['Proxy-Authorization'] = f'Basic {encoded_user_pass}'


在此示例中,我们使用了 WebScrapingApi 代理服务器。

webscrapingapi.proxy_type=residential.render_js=1 中的 username 是代理认证用户名,<API_KEY> 是密码。

您可以在 https://www.webscrapingapi.com/ 注册新账户以获取免费的 API_KEY

定义好 ProxyMiddleware 后,我们只需在 settings.py 文件中将其启用为 DOWNLOAD_MIDDLEWARE 即可。

DOWNLOADER_MIDDLEWARES = {

    'myproject.middlewares.ProxyMiddleware': 1,

}

就这样。如您所见,使用 Scrapy 进行网页抓取可以大大简化我们的工作。

总结

我们成功了!我们构建了一个能够从 Urban Dictionary 中提取词条定义的爬虫,并在过程中深入了解了 Scrapy。从创建自定义项到利用中间件和管道,我们证明了使用 Scrapy 进行网页爬取是多么强大且灵活。而最棒的是?还有更多内容等待你去探索。

恭喜你与我一同走完了这段旅程。现在,你应该对自己的能力充满信心,能够应对任何即将到来的网络爬取项目。 请记住,网络爬取并不一定令人望而生畏或难以应对。只要拥有合适的工具和知识,这将是一次既有趣又富有成就感的体验。如果您需要帮助,请随时通过 https://www.webscrapingapi.com/ 联系我们。我们精通网络爬取技术,非常乐意为您提供力所能及的协助。

关于作者
Mihai Maxim, 全栈开发工程师 @ WebScrapingAPI
Mihai Maxim全栈开发工程师

米海·马克西姆(Mihai Maxim)是 WebScrapingAPI 的全栈开发工程师,他在产品各领域均有贡献,并协助为该平台构建可靠的工具和功能。

开始构建

准备好扩展您的数据收集规模了吗?

加入2,000多家企业,使用WebScrapingAPI在无需任何基础设施开销的情况下,以企业级规模提取网络数据。