百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

使用 Scrapy 轻松抓取网页

csdh11 2024-11-30 14:14 4 浏览

Scrapy 是一个很棒的开源 Python 网页抓取框架。它处理大规模 Web 抓取时最常见的用例:

  • 多线程
  • 爬行(从链接到链接)
  • 提取数据
  • 证实
  • 保存到不同的格式/数据库
  • 还有很多

Scrapy 与 Requests / BeautifulSoup 等其他常用库之间的主要区别在于它允许您以优雅的方式解决常见的网络抓取问题。

Scrapy 的缺点是学习曲线陡峭,有很多东西要学,但这就是我们来这里的目的:)

在本教程中,我们将创建两个不同的网络爬虫,一个简单的从电子商务产品页面提取数据,一个更“复杂”的爬取整个电子商务目录!

基本概述

您可以使用pip安装 Scrapy 。不过要小心,Scrapy 文档强烈建议将其安装在专用的虚拟环境中,以避免与系统包冲突。

我正在使用 Virtualenv 和 Virtualenvwrapper:

mkvirtualenv scrapy_env

pip install Scrapy

您现在可以使用以下命令创建一个新的 Scrapy 项目:

scrapy startproject product_scraper

这将为项目创建所有必要的样板文件。

├── product_scraper
│   ├── __init__.py
│   ├── __pycache__
│   ├── items.py
│   ├── middlewares.py
│   ├── pipelines.py
│   ├── settings.py
│   └── spiders
│       ├── __init__.py
│       └── __pycache__
└── scrapy.cfg

以下是这些文件和文件夹的简要概述:

  • items.py是提取数据的模型。您可以定义将继承scrapy Item 类的自定义模型(如产品)。
  • middlewares.py用于更改请求/响应生命周期的中间件。例如,您可以创建一个中间件来轮换用户代理,或者使用像 ScrapingBee 这样的 API 而不是自己执行请求。
  • pipelines.py在 Scrapy 中,管道用于处理提取的数据、清理 HTML、验证数据并将其导出为自定义格式或将其保存到数据库中。
  • /spiders是一个包含 Spider 类的文件夹。对于 Scrapy,Spider 是定义如何抓取网站的类,包括要遵循的链接以及如何为这些链接提取数据。
  • scrapy.cfg是用于更改某些设置的配置文件

抓取产品

在这个例子中,我们将从一个虚拟的电子商务网站上抓取一个产品。这是我们要抓取的第一个产品:


https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/

我们将提取产品名称、图片、价格和描述。

Scrapy Shell

Scrapy 带有一个内置的 shell,可以帮助您实时尝试和调试您的抓取代码。您可以使用它快速测试您的 XPath 表达式/CSS 选择器。这是一个非常酷的工具来编写你的网络爬虫,我一直在使用它!

您可以将 Scrapy Shell 配置为使用另一个控制台,而不是像 IPython 这样的默认 Python 控制台。您将获得自动完成功能和其他不错的好处,例如彩色输出。

为了在您的 scrapy Shell 中使用它,您需要将此行添加到您的 scrapy.cfg 文件中:

shell = ipython

配置完成后,您可以开始使用 scrapy shell:

$ scrapy shell --nolog
[s] Available Scrapy objects:
[s]   scrapy     scrapy module (contains scrapy.Request, scrapy.Selector, etc)
[s]   crawler    <scrapy.crawler.Crawler object at 0x108147eb8>
[s]   item       {}
[s]   settings   <scrapy.settings.Settings object at 0x108d10978>
[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
In [1]:

我们可以通过简单地开始获取 URL:

fetch('https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/')

这将从获取 /robot.txt 文件开始。

[scrapy.core.engine] DEBUG: Crawled (404) <GET https://clever-lichterman-044f16.netlify.com/robots.txt> (referer: None)

在这种情况下,没有任何 robots.txt,这就是为什么我们可以看到 404 HTTP 代码。如果有 robots.txt,默认情况下 Scrapy 将遵循规则。

您可以通过在 settings.py 中更改此设置来禁用此行为:

ROBOTSTXT_OBEY = True

那么你应该有这样的日志:

[scrapy.core.engine] DEBUG: Crawled (200) <GET https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/> (referer: None)

您现在可以查看您的响应对象、响应标头,并尝试使用不同的 XPath 表达式/CSS 选择器来提取您想要的数据。

您可以直接在浏览器中查看响应:

view(response)

请注意,由于许多不同的原因,该页面将在您的浏览器中呈现不佳。这可能是 CORS 问题、未执行的 Javascript 代码或无法在本地运行的资产的相对 URL。

scrapy shell 就像一个普通的 Python shell,所以不要犹豫,在其中加载你最喜欢的脚本/函数。

提取数据

Scrapy 默认不执行任何 Javascript,因此如果您尝试抓取的网站使用 Angular / React.js 等前端框架,您可能无法访问所需的数据。

现在让我们尝试一些 XPath 表达式来提取产品标题和价格:



为了提取价格,我们将使用XPath 表达式,我们选择带有类的 div 之后的第一个css类 "my-4"

In [16]: response.xpath("//div[@class='my-4']/span/text()").get()
Out[16]: '20.00#39;

我还可以使用 CSS 选择器:

In [21]: response.css('.my-4 span::text').get()
Out[21]: '20.00#39;

创建一个 Scrapy Spider

Spider 是您定义抓取(需要抓取哪些链接/URL)和抓取(提取什么)行为的类。

以下是Spider用来抓取网站的不同步骤:

  • 它首先查看类属性start_urls,然后使用 start_requests() 方法调用这些 URL。如果您需要更改 HTTP 动词,可以覆盖此方法,向请求添加一些参数(例如,发送 POST 请求而不是 GET)。
  • 然后它将为每个 URL 生成一个 Request 对象,并将响应发送到回调函数 parse()
  • 然后 parse() 方法将提取数据(在我们的例子中是产品价格、图像、描述、标题)并返回一个字典、一个 Item 对象、一个 Request 或一个 iterable。

您可能想知道为什么 parse 方法可以返回这么多不同的对象。是为了灵活性。假设您要抓取一个没有任何站点地图的电子商务网站。您可以从抓取产品类别开始,因此这将是第一个解析方法。

然后,此方法将为每个产品类别产生一个 Request 对象,以新的回调方法 parse2() 对于每个类别,您需要处理分页然后对于每个产品,生成一个 Item 的实际抓取,以便第三个解析函数。

使用 Scrapy,您可以将抓取的数据作为简单的 Python 字典返回,但最好使用内置的 Scrapy Item类。它是我们抓取数据的一个简单容器,Scrapy 将查看该项目的字段以进行许多操作,例如将数据导出为不同格式(JSON / CSV ...)、项目管道等。

所以这是一个基本的产品类:

import scrapy

class Product(scrapy.Item):
    product_url = scrapy.Field()
    price = scrapy.Field()
    title = scrapy.Field()
    img_url = scrapy.Field()

现在我们可以使用命令行助手生成Spider:

scrapy genspider myspider mydomain.com

或者您可以手动完成并将您的 Spider 代码放在 /spiders 目录中。

Scrapy 中有不同类型的 Spider 来解决最常见的网页抓取用例:

  • Spider : 它需要一个 start_urls 列表并用一种parse方法抓取每个列表。
  • CrawlSpider 抓取遵循由一组规则定义的链接
  • SitemapSpider提取站点地图中定义的 URL
  • 还有很多
# -*- coding: utf-8 -*-
import scrapy

from product_scraper.items import Product

class EcomSpider(scrapy.Spider):
    name = 'ecom_spider'
    allowed_domains = ['clever-lichterman-044f16.netlify.com']
    start_urls = ['https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/']

    def parse(self, response):
        item = Product()
        item['product_url'] = response.url
        item['price'] = response.xpath("//div[@class='my-4']/span/text()").get()
        item['title'] = response.xpath('//section[1]//h2/text()').get()
        item['img_url'] = response.xpath("//div[@class='product-slider']//img/@src").get(0)
        return item

在这个EcomSpider类中,有两个必需的属性:

  • name这是Spider的名字(你可以使用它来运行scrapy runspider spider_name
  • start_urls这是起始网址

allowed_domains当您使用可以跟踪不同域上的链接的 CrawlSpider 时,这是可选的但很重要。

然后,我刚刚使用 XPath 表达式填充了 Product 字段,以提取我们之前看到的我想要的数据,然后我们返回该项目。

您可以按如下方式运行此代码以将结果导出为 JSON(也可以导出为 CSV)

scrapy runspider ecom_spider.py -o product.json

然后你应该得到一个不错的 JSON 文件:

[
  {
    "product_url": "https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/",
    "price": "20.00#34;,
    "title": "Taba Cream",
    "img_url": "https://clever-lichterman-044f16.netlify.com/images/products/product-2.png"
  }
]

从 Web 提取数据时,您可能会遇到两个常见问题:

  • 对于同一个网站,页面布局和底层 HTML 可以不同。如果你抓取一个电子商务网站,你通常会有一个正常的价格和一个折扣价,使用不同的 XPath/CSS 选择器。
  • 数据可能很混乱,需要某种后处理,同样对于电子商务网站,它可能是显示价格的方式,例如($1.00, $1, $1,00)

Scrapy 有一个内置的解决方案Item Loaders。这是填充我们的 Product 对象的一种有趣方式。

您可以将多个 XPath 表达式添加到同一个 Item 字段,它会按顺序对其进行测试。默认情况下,如果找到多个 XPath,它会将它们全部加载到一个列表中。

您可以在Scrapy 文档中找到许多输入和输出处理器的示例。

当您需要转换/清理提取的数据时,它非常有用。例如,从价格中提取货币,将一个单位转换为另一个单位(厘米单位为米,摄氏度为华氏度)......

在我们的网页中,我们可以找到具有不同 XPath 表达式的产品标题://title//section[1]//h2/text()

在这种情况下,您可以使用以下方法和 Itemloader:

def parse(self, response):
    l = ItemLoader(item=Product(), response=response)
    l.add_xpath('price', "//div[@class='my-4']/span/text()")
    l.add_xpath('title', '//section[1]//h2/text()')
    l.add_xpath('title', '//title')
    l.add_value('product_url', response.url)
    return l.load_item()

通常您只需要第一个匹配的 XPath,因此您需要将它添加output_processor=TakeFirst()到您的项目的字段构造函数中。

在我们的例子中,我们只想要每个字段的第一个匹配的 XPath,因此更好的方法是创建我们自己的 Item Loader 并声明一个默认的 output_processor 来获取第一个匹配的 XPath:

from scrapy.loader import ItemLoader
from scrapy.loader.processors import TakeFirst, MapCompose, Join

def remove_dollar_sign(value):
    return value.replace('#39;, '')

class ProductLoader(ItemLoader):
    default_output_processor = TakeFirst()
    price_in = MapCompose(remove_dollar_sign)

我还添加了price_in一个输入处理器,用于从价格中删除美元符号。我正在使用MapComposewhich 是一个内置处理器,它需要一个或多个函数按顺序执行。您可以为 . 惯例是在您的 Item 字段的名称中添加_in_out添加输入或输出处理器。

还有更多处理器,您可以在文档中了解更多信息

抓取多个页面

现在我们知道如何抓取单个页面,是时候学习如何抓取多个页面了,比如整个产品目录。正如我们之前看到的,有不同种类的Spider。

当您想抓取整个产品目录时,您应该首先查看的是站点地图。站点地图正是为此而构建的,以向网络爬虫展示网站的结构。

大多数情况下,您可以在base_url/sitemap.xml. 解析站点地图可能很棘手,Scrapy 再次为您提供帮助。

在我们的例子中,您可以在此处找到站点地图:https ://clever-lichterman-044f16.netlify.com/sitemap.xml

如果我们查看站点地图,就会有许多我们不感兴趣的 URL,例如主页、博客文章等:

<url>
  <loc>
  https://clever-lichterman-044f16.netlify.com/blog/post-1/
  </loc>
  <lastmod>2019-10-17T11:22:16+06:00</lastmod>
</url>
<url>
  <loc>
  https://clever-lichterman-044f16.netlify.com/products/
  </loc>
  <lastmod>2019-10-17T11:22:16+06:00</lastmod>
</url>
<url>
  <loc>
  https://clever-lichterman-044f16.netlify.com/products/taba-cream.1/
  </loc>
  <lastmod>2019-10-17T11:22:16+06:00</lastmod>
</url>

幸运的是,我们可以过滤 URL 以仅解析那些与某些模式匹配的 URL,这真的很容易,这里我们只需要 URL 中包含/products/的 URL:

class SitemapSpider(SitemapSpider):
    name = "sitemap_spider"
    sitemap_urls = ['https://clever-lichterman-044f16.netlify.com/sitemap.xml']
    sitemap_rules = [
        ('/products/', 'parse_product')
    ]

    def parse_product(self, response):
        # ... scrape product ...

您可以按如下方式运行此蜘蛛以抓取所有产品并将结果导出到 CSV 文件: scrapy runspider sitemap_spider.py -o output.csv

现在,如果该网站没有任何站点地图怎么办?再一次,Scrapy 有一个解决方案!

让我给你介绍一下CrawlSpider......

CrawlSpider 将从start_urls列表开始爬取目标网站。然后对于每个 url,它将根据Rule. 在我们的例子中很简单,产品具有相同的 URL 模式/products/product_title,所以我们只需要过滤这些 URL。

import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from product_scraper.productloader import ProductLoader
from product_scraper.items import Product

class MySpider(CrawlSpider):
    name = 'crawl_spider'
    allowed_domains = ['clever-lichterman-044f16.netlify.com']
    start_urls = ['https://clever-lichterman-044f16.netlify.com/products/']

    rules = (
        
        Rule(LinkExtractor(allow=('products', )), callback='parse_product'),
    )

    def parse_product(self, response):
      # .. parse product 

如您所见,所有这些内置的 Spider 都非常易于使用。从头开始会复杂得多。

使用 Scrapy,您不必考虑爬取逻辑,例如将新 URL 添加到队列、跟踪已解析的 URL、多线程......

结论

在这篇文章中,我们大致了解了如何使用 Scrapy 抓取网页,以及它如何解决您最常见的网页抓取挑战。当然,我们只触及了表面,还有更多有趣的东西需要探索,比如中间件、导出器、扩展、管道!

如果您一直在使用 BeautifulSoup / Requests 等工具更多地“手动”进行网络抓取,那么很容易理解 Scrapy 如何帮助节省时间并构建更易于维护的抓取工具。

相关推荐

Micheal Nielsen&#39;s神经网络学习之二

依然是跟着MichaelNielsen的神经网络学习,基于前一篇的学习,已经大概明白了神经网络的基本结构和BP算法,也能通过神经网络训练数字识别功能,之后我试验了一下使用神经网络训练之前的文本分类,...

CocoaPods + XCTest进行单元测试 c单元测试工具

在使用XCTest进行单元测试时,我们经常会遇到一些CocoaPods中的开源框架的调用,比如“Realm”或“Alamofire”在测试的时候,如果配置不当,会导致“frameworknotfo...

Java基础知识回顾第四篇 java基础讲解

1、&和&&的区别作为逻辑运算符:&(不管左边是什么,右边都参与运算),&&(如果左边为false,右边则不参与运算,短路)另外&可作为位运算符...

项目中的流程及类似业务的设计模式总结

说到业务流程,可能是我做过的项目中涉及业务最多的一个方面了。除了在流程设计之外,在一些考核系统、产业审批、还有很多地方,都用到相似的设计思路,在此一并总结一下。再说到模式,并不是因为流行才用这个词,而...

联想三款显示器首批获得 Eyesafe Certified 2.0 认证

IT之家7月31日消息,据外媒报道,三款全新联想显示器是全球首批满足EyesafeCertified2.0的设备。据报道,联想获得EyesafeCertified2.0认证的显...

maven的生命周期,插件介绍(二) 一个典型的maven构建生命周期

1.maven生命周期一个完整的项目构建过程通常包括清理、编译、测试、打包、集成测试、验证、部署等步骤,Maven从中抽取了一套完善的、易扩展的生命周期。Maven的生命周期是抽象的,其中的具体任务都...

多线程(3)-基于Object的线程等待与唤醒

概述在使用synchronized进行线程同步中介绍了依赖对象锁定线程,本篇文章介绍如何依赖对象协调线程。同synchronized悲观锁一样,线程本身不能等待与唤醒,也是需要对象才能完成等待与唤醒的...

jquery mobile + 百度地图 + phonegap 写的一个&quot;校园助手&quot;的app

1jquerymobile+百度地图+phonegap写的一个"校园助手"的app,使用的是基于Flat-UI的jQueryMobile,请参考:https://github.com/...

Apache 服务启动不了 apache系统服务启动不了

{我是新手,从未遇到此问题,请各位大大勿喷}事由:今天早上上班突然发现公司网站出现问题。经过排查,发现是Apache出现问题。首先检查配置文件没有出问题后,启动服务发现Apache服务能启动,但是没法...

健康债和技术债都不能欠 公众号: 我是攻城师(woshigcs)

在Solr4.4之后,Solr提供了SolrCloud分布式集群的模式,它带来的主要好处是:(1)大数据量下更高的性能(2)更好扩展性(3)更高的可靠性(4)更简单易用什么时候应该使用Sol...

Eye Experience怎么用?HTC告诉你 eyebeam怎么用

IT之家(www.ithome.com):EyeExperience怎么用?HTC告诉你HTC上周除了发布HTCDesireEYE自拍机和HTCRE管状运动相机之外,还发布了一系列新的智能手机...

Android系统应用隐藏和应用禁止卸载

1、应用隐藏与禁用Android设置中的应用管理器提供了一个功能,就是【应用停用】功能,这是针对某些系统应用的。当应用停用之后,应用的图标会被隐藏,但apk还是存在,不会删除,核心接口就是Packag...

计算机软件技术分享--赠人玫瑰,手遗余香

一、Netty介绍Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty...

Gecco爬虫框架的线程和队列模型 爬虫通用框架

简述爬虫在抓取一个页面后一般有两个任务,一个是解析页面内容,一个是将需要继续抓取的url放入队列继续抓取。因此,当爬取的网页很多的情况下,待抓取url的管理也是爬虫框架需要解决的问题。本文主要说的是g...

一点感悟(一) 初识 初读感知的意思

时间过得很快,在IT业已从业了两年多。人这一辈子到底需要什么,在路边看着人来人往,大部分人脸上都是很匆忙。上海真是一个魔都,它有魅力,有底蕴,但是一个外地人在这里扎根置业,真的是举全家之力,还贷3...