专业的JAVA编程教程与资源

网站首页 > java教程 正文

Python网络爬虫框架的总结(python中的爬虫框架)

temp10 2025-01-31 15:26:06 java教程 13 ℃ 0 评论

前言

虽然使用reqeuests和bs4可以处理网站数据获取但是当你要爬去大批量的网页时,单独的使用reqeuests就显得力不从心了。因为这是两个层面的事情,解决的问题是不一样的。

网络爬虫

网络爬虫是一种强大的技术,通过查找一个或多个域名的所有 URL 来从 Web 网站收集需要的数据。Python 有一些流行的网络爬虫库和框架。

Python网络爬虫框架的总结(python中的爬虫框架)

介绍简单的例子,使用两个库在 Python 中从头开始构建一个简单的网络爬虫:RequestsBeautiful Soup。接着使用 Scrapy 构建一个示例爬虫,从 IMDb 收集电影元数据,并查看 Scrapy 如何扩展到拥有数百万页面的网站。

什么是网络爬虫?

网络爬虫和网络抓取是两个不同但相关的概念。网页爬虫是网页抓取的一个组成部分,爬虫逻辑查找要由抓取工具代码处理的 URL。

网络爬虫从要访问的 URL 列表开始,称为种子。对于每个 URL,爬网程序会在 HTML 中查找链接,根据某些条件过滤这些链接,并将新链接添加到队列中。所有 HTML 或某些特定信息都被提取出来,由不同的管道进行处理。

其实简单的殂就是我们打开淘宝首页,有很多个链接可以点击,打开每个链接又会进入不同的链接,这些子链接也有更多的链接。爬虫可以处理这个形成蜘蛛网一样的链接。

网络爬虫策略

网络爬虫只会访问一部分网页,具体取决于爬虫预算,该预算可以是每个域的最大网页数、深度或执行时间。

许多网站都提供了一个机器人.txt文件,以指示可以抓取网站的哪些路径,哪些是禁止的。还有sitemap.xml,它比robots.txt更明确一些,专门指示机器人应该抓取哪些路径,并为每个URL提供额外的元数据。

流行的网络爬虫用例包括:

  • 搜索引擎(例如 Googlebot、Bingbot、Yandex Bot 等)收集了 Web 重要部分的所有 HTML。此数据已编制索引,使其可搜索。
  • 除了收集 HTML 之外,SEO 分析工具还收集元数据,例如响应时间、检测损坏页面的响应状态以及收集反向链接的不同域之间的链接。
  • 价格监控工具抓取电子商务网站以查找产品页面并提取元数据,尤其是价格。然后定期重新访问产品页面。
  • Common Crawl 维护一个开放的 Web 爬虫数据存储库。例如,2022 年 5 月的档案包含 34.5 亿个网页。


从头开始在 Python 中构建一个简单的网络爬虫

要在 Python 中构建一个简单的网络爬虫,至少需要一个库来从 URL 下载 HTML,另一个库来提取链接。Python 提供了用于执行 HTTP 请求的标准库 urllib 和用于解析 HTML 的 html.parser。可以在 Github 上找到一个仅使用标准库构建的示例 Python 爬虫。

还有其他流行的库,例如 RequestsBeautiful Soup,它们可以在编写 HTTP 请求和处理 HTML 文档时提供改进的开发人员体验。如果您想了解更多信息,可以查看有关最佳 Python HTTP 客户端的指南。

本地安装这两个库。

pip install requests bs4


import logging
from urllib.parse import urljoin
import requests
from bs4 import BeautifulSoup

logging.basicConfig(
    format='%(asctime)s %(levelname)s:%(message)s',
    level=logging.INFO)

class Crawler:

    def __init__(self, urls=[]):
        self.visited_urls = []
        self.urls_to_visit = urls

    def download_url(self, url):
        return requests.get(url).text

    def get_linked_urls(self, url, html):
        soup = BeautifulSoup(html, 'html.parser')
        for link in soup.find_all('a'):
            path = link.get('href')
            if path and path.startswith('/'):
                path = urljoin(url, path)
            yield path

    def add_url_to_visit(self, url):
        if url not in self.visited_urls and url not in self.urls_to_visit:
            self.urls_to_visit.append(url)

    def crawl(self, url):
        html = self.download_url(url)
        for url in self.get_linked_urls(url, html):
            self.add_url_to_visit(url)

    def run(self):
        while self.urls_to_visit:
            url = self.urls_to_visit.pop(0)
            logging.info(f'Crawling: {url}')
            try:
                self.crawl(url)
            except Exception:
                logging.exception(f'Failed to crawl: {url}')
            finally:
                self.visited_urls.append(url)

if __name__ == '__main__':
    Crawler(urls=['https://www.imdb.com/']).run()

(使用 Requests 库、使用 Beautiful Soup 库和过滤 URL),然后继续使用我们的 IMDb 起始 URL 实例化该类并调用其方法。Crawlerdownload_urlget_linked_urlsadd_url_to_visitrun()

只要 中存在待处理的 URL,就会运行,会将每个 URL 传递给 ,提取任何链接,并将它们添加到 - 冲洗并重复。runurls_to_visitcrawl()urls_to_visit

要运行我们的爬虫,只需在命令行中输入此命令即可。

python crawler.py

爬网程序为每个访问过的 URL 记录一行。

INFO:Crawling: https://www.imdb.com/
INFO:Crawling: https://www.imdb.com/?ref_=nv_home
INFO:Crawling: https://www.imdb.com/calendar/?ref_=nv_mv_cal
INFO:Crawling: https://www.imdb.com/list/ls016522954/?ref_=nv_tvv_dvd
INFO:Crawling: https://www.imdb.com/chart/top/?ref_=nv_mv_250
INFO:Crawling: https://www.imdb.com/chart/moviemeter/?ref_=nv_mv_mpm
INFO:Crawling: https://www.imdb.com/feature/genre/?ref_=nv_ch_gr

代码非常简单,但在成功抓取完整网站之前,有许多性能和可用性问题需要解决。

  • 爬网程序速度较慢,不支持并行性。从时间戳可以看出,抓取每个 URL 大约需要一秒钟。每次爬网程序发出请求时,它都会等待响应,不会执行太多其他操作。
  • 下载 URL 逻辑没有重试机制,URL 队列不是真正的队列,并且对于大量 URL 效率不高。
  • 链接提取逻辑不支持通过删除 URL 查询字符串参数来标准化 URL,不处理相对锚点/片段 URL(即 ),也不支持按域过滤 URL 或过滤掉对静态文件的请求。href="#myanchor"
  • 抓取工具不会识别自己,并忽略漫游器 .txt 文件。


使用 Scrapy 进行网络爬虫

Scrapy 是最受欢迎的网络抓取和抓取 Python 框架,。

Scrapy 具有多组件架构。通常实现至少两个不同的类:Spider 和 Pipeline。Web 抓取可以被认为是一种 ETL,在其中从 Web 中提取数据并将其加载到存储中。蜘蛛提取数据,管道将其加载到存储中。转换可以发生在爬虫和管道中,

可以在组件之间添加爬虫和下载器中间件,如下图所示。



Scrapy 架构概述 [源代码]

from scrapy.spiders import Spider

class ImdbSpider(Spider):
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['https://www.imdb.com/']

    def parse(self, response):
        pass

Scrapy 还提供了几个通用的爬虫类:CrawlSpider、XMLFeedSpider、CSVFeedSpider 和 SitemapSpider。CrawlSpider 类继承自 Spider 基类,并提供额外的 rules 属性来定义如何抓取网站。每个规则都使用 LinkExtractor 来指定从每个页面中提取哪些链接。接下来,我们将通过为 IMDb(互联网电影数据库)构建爬虫来了解如何使用它们中的每一个。

为 IMDb 构建一个示例 Scrapy 爬虫

在尝试抓取 IMDb 之前,检查了 IMDb 机器人 .txt 文件,看看允许哪些 URL 路径。robots 文件仅禁止所有用户代理的 26 个路径。Scrapy 会事先读取 robot.txt 文件,并在 ROBOTSTXT_OBEY 设置为 时遵守该文件。使用 Scrapy 命令生成的所有项目都是这种情况。truestartproject

scrapy startproject scrapy_crawler

此命令使用默认的 Scrapy 项目文件夹结构创建一个新项目。

scrapy_crawler/

├── scrapy.cfg
└── scrapy_crawler
    ├── __init__.py
    ├── items.py
    ├── middlewares.py
    ├── pipelines.py
    ├── settings.py
    └── spiders
        ├── __init__.py

然后可以使用规则创建一个蜘蛛来提取所有链接。scrapy_crawler/spiders/imdb.py

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor

class ImdbCrawler(CrawlSpider):
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['https://www.imdb.com/']
    rules = (Rule(LinkExtractor()),)

现在,只需使用命令启动爬虫scrapy

scrapy crawl imdb --logfile imdb.log

将获得大量日志,包括每个请求的一个日志。在浏览日志时,我注意到,即使我们设置为仅抓取 https://www.imdb.com 下的网页,也会有对外部域(例如 amazon.com)的请求。allowed_domains


[scrapy.downloadermiddlewares.redirect] DEBUG: Redirecting (302) to <GET https://www.amazon.com/b/?&node=5160028011&ref_=ft_iba> from <GET [https://www.imdb.com/whitelist-offsite?url=https%3A%2F%2Fwww.amazon.com%2Fb%2F%3F%26node%3D5160028011%26ref_%3Dft_iba&page-action=ft-iba&ref=ft_iba](https://www.imdb.com/whitelist-offsite?url=https%3A%2F%2Fwww.amazon.com%2Fb%2F%3F%26node%3D5160028011%26ref_%3Dft_iba&page-action=ft-iba&ref=ft_iba)>

IMDb 将外部域下的路径重定向到外部域。有一个开放的 Scrapy Github 问题,该问题显示外部 URL 在应用之前不会被过滤掉。为了解决这个问题,可以将链接提取器配置为跳过以两个正则表达式开头的 URL。/whitelist-offsite/whitelistOffsiteMiddlewareRedirectMiddleware

class ImdbCrawler(CrawlSpider):
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['https://www.imdb.com/']
    rules = (
        Rule(LinkExtractor(
            deny=[
                re.escape('https://www.imdb.com/offsite'),
                re.escape('https://www.imdb.com/whitelist-offsite'),
            ],
        )),
    )

Rule类支持多个参数来过滤 URL。例如可以忽略特定的文件扩展名,并通过对查询字符串进行排序或折叠来减少重复 URL 的数量。LinkExtractor

如果找不到用例的特定参数,则可以使用 LinkExtractor 或 Rule 的参数。例如两次获得相同的页面,一次是纯 URL,另一次是附加查询字符串参数。process_valueprocess_links

  • https://www.imdb.com/name/nm1156914/
  • https://www.imdb.com/name/nm1156914/?mode=desktop&ref_=m_ft_dsk

为了限制抓取的 URL 的数量,可以使用 w3lib 库中的 url_query_cleaner 函数从 URL 中删除所有查询字符串,并在 .process_links

from w3lib.url import url_query_cleaner

def process_links(links):
    for link in links:
        link.url = url_query_cleaner(link.url)
        yield link

class ImdbCrawler(CrawlSpider):

    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['https://www.imdb.com/']
    rules = (
        Rule(LinkExtractor(
            deny=[
                re.escape('https://www.imdb.com/offsite'),
                re.escape('https://www.imdb.com/whitelist-offsite'),
            ],
        ), process_links=process_links),
    )

现在已经限制了要处理的请求数,可以添加一种方法来从每个页面中提取数据并将其传递到管道进行存储。例如可以在不同的管道中对其进行处理,也可以选择 HTML 元数据。parse_itemresponse.text

要在标头标签中选择 HTML 元数据,可以指定自己的 XPath 表达式,最好使用库 extract 从 HTML 页面中提取所有元数据。

可以使用 .pip install extruct

import re
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from w3lib.url import url_query_cleaner
import extruct

def process_links(links):
    for link in links:
        link.url = url_query_cleaner(link.url)
        yield link

class ImdbCrawler(CrawlSpider):
    name = 'imdb'
    allowed_domains = ['www.imdb.com']
    start_urls = ['https://www.imdb.com/']
    rules = (
        Rule(
            LinkExtractor(
                deny=[
                    re.escape('https://www.imdb.com/offsite'),
                    re.escape('https://www.imdb.com/whitelist-offsite'),
                ],
            ),
            process_links=process_links,
            callback='parse_item',
            follow=True
        ),
    )

    def parse_item(self, response):
        return {
            'url': response.url,
            'metadata': extruct.extract(
                response.text,
                response.url,
                syntaxes=['opengraph', 'json-ld']
            ),
        }

该属性设置为,以便 Scrapy 仍然遵循每个响应的所有链接,配置了 extruct 以仅提取 Open Graph 元数据和 JSON-LD,这是一种在 Web 中使用 JSON 对链接数据进行编码的流行方法,由 IMDb 使用。可以运行爬网程序并将项目以 JSON 行格式存储到文件中。followTrue

scrapy crawl imdb --logfile imdb.log -o imdb.jl -t jsonlines

输出文件包含每个已爬网项的一行。例如,对于从 HTML 中的标记中获取的电影,提取的 Open Graph 元数据如下所示。imdb.jl<meta>


{
    "url": "http://www.imdb.com/title/tt2442560/",
    "metadata": {"opengraph": [{
         "namespace": {"og": "http://ogp.me/ns#"},
         "properties": [
             ["og:url", "http://www.imdb.com/title/tt2442560/"],
             ["og:image", "https://m.media-amazon.com/images/M/MV5BMTkzNjEzMDEzMF5BMl5BanBnXkFtZTgwMDI0MjE4MjE@._V1_UY1200_CR90,0,630,1200_AL_.jpg"],
             ["og:type", "video.tv_show"],
             ["og:title", "Peaky Blinders (TV Series 2013\u2013 ) - IMDb"],
             ["og:site_name", "IMDb"],
             ["og:description", "Created by Steven Knight.  With Cillian Murphy, Paul Anderson, Helen McCrory, Sophie Rundle. A gangster family epic set in 1900s England, centering on a gang who sew razor blades in the peaks of their caps, and their fierce boss Tommy Shelby."]
        ]
   }]}
}

单个项目的 JSON-LD 太长,无法包含在文章中,以下是 Scrapy 从标签中提取的内容的示例。<script type="application/ld+json">

"json-ld": [
    {
        "@context": "http://schema.org",
        "@type": "TVSeries",
        "url": "/title/tt2442560/",
        "name": "Peaky Blinders",
        "image": "https://m.media-amazon.com/images/M/MV5BMTkzNjEzMDEzMF5BMl5BanBnXkFtZTgwMDI0MjE4MjE@._V1_.jpg",
        "genre": ["Crime","Drama"],
        "contentRating": "TV-MA",
        "actor": [
            {
                "@type": "Person",
                "url": "/name/nm0614165/",
                "name": "Cillian Murphy"
            },
            ...
        ]
        ...
    }
]

通过按顺序点击过滤器,爬虫会生成内容相同的网址,只是过滤器的应用顺序不同。

  • https://www.imdb.com/name/nm2900465/videogallery/ content_type-拖车/related_titles-TT0479468
  • https://www.imdb.com/name/nm2900465/videogallery/ related_titles-TT0479468/content_type-拖车

长过滤器和搜索 URL 是一个难题,可以通过使用 Scrapy 设置限制 URL 的长度来部分解决,URLLENGTH_LIMIT。

以 IMDb 为例来展示在 Python 中构建网络爬虫的基础知识。如果需要来自 IMDb 的特定数据,可以查看提供每日 IMDb 数据导出的 IMDb 数据集项目或 Cinemagoer,

大规模 Web 爬虫

如果尝试抓取像 IMDb 这样拥有超过 1.3 亿个页面的大型网站(至少根据 Google 的数据),请务必通过调整您的爬虫并相应地调整其设置来负责任地抓取。

  1. USER_AGENT - 允许您指定用户代理并提供可能的联系方式
  2. DOWNLOAD_DELAY - 指定爬网程序在请求之间应等待的秒数
  3. CONCURRENT_REQUESTS_PER_DOMAIN - 指示爬网程序应向一个站点发送的最大并发请求数
  4. AUTOTHROTTLE_ENABLED - 启用自动和动态请求限制

请注意,默认情况下,Scrapy 爬网针对单个域进行了优化。如果要对多个网域进行爬网,请检查这些设置以针对广泛爬网进行优化,包括将默认爬网顺序从深度优先更改为呼吸优先。要限制抓取预算,可以使用关闭爬虫扩展程序的CLOSESPIDER_PAGECOUNT设置来限制请求数。

使用默认设置,Scrapy 每分钟为 IMDb 等网站抓取约 600 个页面。以这种速度,用一个机器人抓取 1.3 亿个页面大约需要半年时间。如果您需要抓取多个网站,最好为每个大型网站或网站组启动单独的爬虫。如果您对分布式 Web 爬网感兴趣,可以阅读开发人员如何使用 20 个 Amazon EC2 机器实例在不到两天的时间内使用 Python 抓取 250M 个页面。

在某些情况下,可能会遇到需要您执行 JavaScript 代码来呈现所有 HTML 的网站。否则,可能无法收集网站上的所有链接。因为现在网站在浏览器中动态渲染内容非常普遍,所以我编写了一个 Scrapy 中间件,用于使用 ScrapingBee 的 API 渲染 JavaScript 页面。

这一点主要是解决这种使用JavaScript动态生成HTML页面的情况。

结论

  • HTML是传统爬虫的关键
  • 新的WEB开发技术使用JavaScript来生成页面,实际爬虫任务比这个复杂
  • requests bs4 是基础的库,
  • scrapy是一个爬虫框架。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表