使用Scrapy从HTML标签中提取数据

2018-09-17 14:49:52 浏览数 (1)

Scrapy是一个用于创建Web爬虫应用的Python框架。它提供了相关编程接口,可以通过识别新链接来抓取Web数据,并可以从下载的内容中提取结构化数据。

本指南将为您提供构建Spider爬虫的说明,它可通过递归方式来检查网站的所有<a>标记并跟踪记录无效的链接。本指南是为3.4或更高版本的Python以及Scrapy 1.4版来编写的,它并不适用于Python 2环境。

准备工作

  1. 熟悉我们的入门指南并完成设Linode主机名和时区的设置步骤。
  2. 本指南将尽可能使用sudo实现指令。请完成“ 保护您的服务器 ”部分以创建标准用户帐户,同时加强SSH访问并删除不必要的网络服务。
  3. 更新您的系统: sudo apt update && sudo apt upgrade -y

注意 本指南是为非root用户编写的。需要提升权限的命令请使用sudo前缀执行。如果您不熟悉该sudo命令,请参阅“ 用户和组”指南。

安装Python 3环境

在包括Debian 9和CentOS 7的大多数系统上,默认的Python版本是2.7,并且需要手动安装pip包安装管理工具。

在Debian 9系统上安装

  1. Debian 9自身同时携带了Python 3.5和2.7,但其中2.7是默认的版本。请修改版本: update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1 update-alternatives --install /usr/bin/python python /usr/bin/python3.5 2
  2. 检查您使用的是否是Python 3版本: python --version
  3. 安装pip,Python包安装管理工具: sudo apt install python3-pip

在CentOS 7系统下安装

  1. 在CentOS系统上,请从EPEL包管理存储库安装Python、PIP和一些依赖项: sudo yum install epel-release sudo yum install python34 python34-pip gcc python34-devel
  2. /usr/bin/python程序链接从原先默认的Python2 替换为新安装的Python 3: sudo rm -f /usr/bin/python sudo ln -s /usr/bin/python3 /usr/bin/python
  3. 检查是否使用了正确的版本: python --version

安装Scrapy

系统级别下安装(不推荐)

虽然系统级别下的安装是最简单的方法,但可能其会与其他需要不同版本库的Python脚本冲突。请在当您的系统仅专用于Scrapy时才使用此方法:

代码语言:txt复制
sudo pip3 install scrapy

在虚拟环境下安装Scrapy

这是推荐的安装方法。Scrapy将安装在一个virtualenv环境中,以防止与系统级别的库发生冲突。

  1. 在CentOS系统上,Python 3版本的virtualenv将随Python一起安装。但在Debian 9上它需要几个步骤进行安装: sudo apt install python3-venv sudo pip3 install wheel
  2. 创建虚拟环境: python -m venv ~/scrapyenv
  3. 激活虚拟环境: source ~/scrapyenv/bin/activate 然后,shell提示符将显示您正在使用的环境。
  4. 在虚拟环境中安装Scrapy。请注意,您不再需要添加sudo前缀,库将仅安装在新创建的虚拟环境中: pip3 install scrapy

创建Scrapy项目

以下所有命令均在虚拟环境中完成。如果您是重新开始会话,请不要忘记重新激活scrapyenv

  1. 创建一个目录来保存您的Scrapy项目: mkdir ~/scrapy cd ~/scrapy scrapy startproject linkChecker
  2. 定位到新的Scrapy项目目录并创建一个Spider爬虫程序。本文进行抓取的模板网站为http://www.example.com,请将其调整到您要抓取的网站。 cd linkChecker scrapy genspider link_checkerwww.example.com 此操作将创建一个带有基本Spider爬虫的~/scrapy/linkChecker/linkChecker/spiders/link_checker.py文件。

注意 以下部分中的所有路径和命令都是基于~/scrapy/linkChecker这个srapy项目目录的。

开启Spider爬虫程序

  1. 开始Spider爬虫程序: scrapy crawl Spider爬虫程序会在Scrapy中注册自己的名称,该名称是在您的Spider类中的name属性中进行制定的。
  2. 启动link_checkerSpider爬虫程序: cd ~/scrapy/linkChecker scrapy crawl link_checker 新创建的Spider爬虫只会下载www.example.com页面,之后我们将创建爬取逻辑。

使用Scrapy Shell

Scrapy提供了两种简单的从HTML中提取内容的方法:

  • response.css()方法使用CSS选择器来获取标签。检索btnCSS类中的所有链接,请使用: response.css("a.btn::attr(href)")
  • response.xpath()方法从XPath查询中获取标签。要检索链接内所有图像的资源地址,请使用: response.xpath("//a/img/@src")

您可以尝试使用交互式的Scrapy shell:

  1. 在您的网页上运行Scrapy shell: scrapy shell http://www.example.com
  2. 对选择器进行测试,直到其结果达到你的预期: response.xpath("//a/@href").extract()

有关选择器的更多信息,请参阅Scrapy选择器文档。

编写爬虫爬取逻辑

Spider爬虫使用parse(self,response)方法来解析所下载的页面。此方法返回一个包含新的URL资源网址的迭代对象,这些新的URL网址将被添加到下载队列中以供将来进行爬取数据和解析。

  • 1.编辑linkChecker/spiders/link_checker.py文件以提取所有<a>标签并获取href链接文本。返回带有yield关键字的URL网址并将其添加到下载队列:
代码语言:txt复制
import scrapy

class LinkCheckerSpider(scrapy.Spider):
    name = 'link_checker'
    allowed_domains = ['www.example.com']
    start_urls = ['http://www.example.com/']

    def parse(self, response):
        """ Main function that parses downloaded pages """
        # 打印spider正在进行的事务
        print(response.url)
        # 获取所有<a>标签
        a_selectors = response.xpath("//a")
        # 对每个标签进行循环操作
        for selector in a_selectors:
            # 解析出链接的文本
            text = selector.xpath("text()").extract_first()
            # 解析出链接的网址
            link = selector.xpath("@href").extract_first()
            # 创建一个新的Request对象
            request = response.follow(link, callback=self.parse)
            # 基于生成器返回该对象
            yield request
  • 2.运行更新后的Spider爬虫: scrapy crawl link_checker 然后,您将看到Spider爬虫爬取所有链接。由于allowd_domains属性的限制,它不会超出www.example.com域。根据网站的大小不同,这可能需要一些时间。如果需要停止进程,请使用Ctrl C指令。

添加Request请求的元信息

Spider爬虫将以递归方式遍历队列中的链接。在解析所下载的页面时,它没有先前解析页面的任何信息,例如哪个页面链接到了新页面。为了将更多信息传递给parse方法,Scrapy提供了一种Request.meta()方法,可以将一些键值对添加到请求中,这些键值对在parse()方法的响应对象中可用。

元信息用于两个目的:

  • 为了使parse方法知道来自触发请求的页面的数据:页面的URL资源网址(from_url)和链接的文本(from_text
  • 为了计算parse方法中的递归层次,来限制爬虫的最大深度。
  • 1.从前一个spider爬虫开始,就添加一个属性来存储最大深度(maxdepth)并将parse函数更新为以下内容:
代码语言:txt复制
# 添加最大深度参数
maxdepth = 2

def parse(self, response):
    # 设置首页的默认元信息
    from_url = ''
    from_text = ''
    depth = 0;
    # 如果有信息的话,解析响应中的源信息
    if 'from' in response.meta:
        from_url = response.meta['from']
    if 'text' in response.meta:
        from_text = response.meta['text']
    if 'depth' in response.meta:
        depth = response.meta['depth']

    # 更新输出逻辑,来展现包含当前页面链接的页面和链接的文本信息
    print(depth, reponse.url, '<-', from_url, from_text, sep=' ')
    # 在还未到达最大深度的情况下才可以浏览标签
    if depth < self.maxdepth:
        a_selectors = response.xpath("//a")
        for selector in a_selectors:
            text = selector.xpath("text()").extract_first()
            link = selector.xpath("@href").extract_first()
            request = response.follow(link, callback=self.parse)
            # 元信息:当前页面的URL资源网络地址
            request.meta['from'] = response.url
            # 元信息:链接的文本信息
            request.meta['text'] = text
            # 元信息:链接的深度
            request.meta['depth'] = depth   1
            yield request
  • 2.运行更新后的spider爬虫:scrapy crawl link_checker 您的爬虫程序爬取深度不能超过两页,并且当所有页面下载完毕将会停止运行。其输出结果将显示链接到下载页面的页面以及链接的文本信息。

设置需处理的HTTP状态

默认情况下,Scrapy爬虫仅解析请求成功的HTTP请求;,在解析过程中需要排除所有错误。为了收集无效的链接,404响应就必须要被解析了。创建valid_urlinvalid_url两个数组,,分别将有效和无效的链接存入。

  • 1.设置在spider爬虫属性handle_httpstatus_list中解析的HTTP错误状态列表: handle_httpstatus_list = [404]
  • 2.更新解析逻辑以检查HTTP状态和填充正确的数组。爬虫程序现在看起来像:
代码语言:txt复制
class LinkCheckerSpider(scrapy.Spider):
    name = "link_checker"
    allowed_domains = ['www.example.com']
    # 设置需要处理的HTTP错误码
    handle_httpstatus_list = [404]
    # 初始化有效和无效链接的数组
    valid_url, invalid_url = [], []
    maxdepth = 2

    def parse(self, response):
        from_url = ''
        from_text = ''
        depth = 0;
        if 'from' in response.meta: from_url = response.meta['from']
        if 'text' in response.meta: from_text = response.meta['text']
        if 'depth' in response.meta: depth = response.met['depth']

        # 出现了404错误,填充无效链接数组
        if response.status == 404:
            self.invalid_url.append({'url': response.url,
                                     'from': from_url,
                                     'text': from_text})
        else:
            # 填充有效链接数组
            self.valid_url.append({'url': response.url,
                                   'from': from_url,
                                   'text': from_text})
            if depth < self.maxdepth:
                a_selectors = response.xpath("//a")
                for selector in a_selectors:
                    text = selector.xpath("text()").extract_first()
                    link = selector.xpath("@href").extract_first()
                    request = response.follow(link, callback=self.parse)
                    request.meta['from'] = response.url;
                    request.meta['text'] = text
                    yield request
  • 3.运行更新后的Spider爬虫: scrapy crawl link_checker 这里的输出信息应该比以前的更多。这两个数组虽然已填充但从并未打印信息到控制台。爬虫程序必须在信息处理程序爬取结束时就转存它们。

设置信息处理程序

Scrapy允许您在爬取过程中的各个点中添加一些处理程序。信息处理程序使用crawler.signals.connect()方法进行设置,crawler对象在Spider类中的from_crawler()方法中可用。

要在爬取过程结束时添加处理程序以打印有关无效链接的信息,请重写from_crawler方法以注册处理signals.spider_closed信号的处理程序:

代码语言:txt复制
# 重写from_crawler方法
@classmethod
def from_crawler(cls, crawler, *args, **kwargs):
    # 回调父方法以保障正常运行
    spider = super(LinkCheckerSpider, cls).from_crawler(crawler, *args, **kwargs)
    # 为spider_closed标记注册spider_closed处理程序
    crawler.signals.connect(spider.spider_closed, signals.spider_closed)
    return spider

# This method is the actual handler
def spider_closed(self):
    # 打印爬取到i信息中的一些有效信息
    print('There are', len(self.valid_url), 'working links and',
          len(self.invalid_url), 'broken links.', sep=' ')
    # 如果有的话,输出所有无效链接
    if len(self.invalid_url) > 0:
        print("Broken links are:")
        for invalid in self.invalid_url:
            print(invalid)

请参阅Scrapy信号文档来获取完整的可用信号列表。

再次运行Spider爬虫,您将在Scrapy统计信息之前看到无效链接的详细信息。

命令行的输入起始URL网址

初始的URL网址在spider爬虫的源代码中是硬编码的。如果我们可以在启动爬虫时就设置它而不是更改代码,效果会更好。scrapy crawl允许通过命令行使用__init__()类构造函数来传递参数。

  • 1.使用url参数向爬虫程序添加__init__()方法:
代码语言:txt复制
# 将url参数添加到自定义构造函数
def __init__(self, url='http://www.example.com', *args, **kwargs):
    # 不要忘记调用父构造函数
    super(LinkCheckerSpider, self).__init__(*args, **kwargs)
    # 使用url参数设置start_urls属性
    self.start_urls = [url]
  • 2.使用-a命令行标志传递Spider爬虫参数: scrapy crawl linkChecker -a url="http://another_example.com"

进行项目设置

爬虫程序的默认Scrapy设置在settings.py文件中定义。请将最大下载大小设置为3 MB,以防止Scrapy下载视频或二进制文件等大文件。

请编辑~/scrapy/linkChecker/linkChecker/settings.py并添加以下行:

移除域名限制

我们的爬虫程序有一个名为allowed_domains的参数来阻止下载不需要的URL 网址。如果没有此属性,爬虫可能会尝试遍历整个Web并且永远不会完成其任务。

如果www.example.com域中与外部域的链接中断,则将不会检测到该链接,因为爬虫不会对其进行爬取信息。删除该allowed_domains属性以添加下载外部网页的自定义逻辑,这不会造成递归浏览其链接。

  • 1.添加URL网址和正则表达式管理包:
代码语言:txt复制
import re
from urllib.parse import urlparse
  • 2.添加domain = ''属性将保存主域。主域未初始化,在其第一次下载时设置为实际URL网址。在HTTP重定向的情况下,实际URL可能与起始URL不同。
  • 3.删除allowed_domains属性
  • 4.初始化parse方法中的domain属性:
代码语言:txt复制
if len(self.domain) == 0:
    parsed_uri = urlparse(response.url)
    self.domain = parsed_uri.netloc
  • 5.更新表达式以添加域检查并对新的URL网址进行深度检查:
代码语言:txt复制
parsed_uri = urlparse(response.url)

# 对新链接采用先前的逻辑
if parsed_uri.netloc == self.domain and depth < self.maxdepth:

请参阅下一节中的完整spider爬虫,之前的相关设置回集成在此代码中。

完全实现的Spider爬虫程序

这是功能齐全的Spider爬虫程序。添加了一些技巧来获取响应域并阻止其他域链接的递归浏览。否则,您的Spider爬虫将尝试解析整个网络!

代码语言:txt复制
import re
from urllib.parse import urlparse

import scrapy
from scrapy import signals


class LinkCheckerSpider(scrapy.Spider):
    name = 'link_checker'
    # 设置需要处理的HTTP错误码
    handle_httpstatus_list = [404]
    valid_url = []
    invalid_url = []
    # 设置最大深度
    maxdepth = 2;
    domain = ''

    def __init__(self, url='http://www.example.com', *args, **kwargs):
        super(LinkCheckerSpider, self).__init__(*args, **kwargs)
        self.start_urls = [url]

    @classmethod
    def from_crawler(cls, crawler, *args, **kwargs):
        spider = super(LinkCheckerSpider, cls).from_crawler(crawler, *args, **kwargs)
        # 为spider_closed标记注册spider_closed处理程序
        crawler.signals.connect(spider.spider_closed, signals.spider_closed)
        return spider

    def spider_closed(self):
        """spider_closed 标签的处理程序"""
        print('----------')
        print('There are', len(self.valid_url), 'working links and',
              len(self.invalid_url), 'broken links.', sep=' ')
        if len(self.invalid_url) > 0:
            print('Broken links are:')
            for invalid in self.invalid_url:
                print(invalid)
        print('----------')

    def parse(self, response):
        """ 解析下载页面的主方法"""
        # 为没有元信息的首页设置默认值
        from_url = ''
        from_text = ''
        depth = 0;
        # 如果有的话,解析响应中的元信息
        if 'from' in response.meta: from_url = response.meta['from']
        if 'text' in response.meta: from_text = response.meta['text']
        if 'depth' in response.meta: depth = response.meta['depth']

        # 如果第一次响应,更新域信息(以管理重定向)
        if len(self.domain) == 0:
            parsed_uri = urlparse(response.url)
            self.domain = parsed_uri.netloc

        # 出现404错误,填充无效链接数组
        if response.status == 404:
            self.invalid_url.append({'url': response.url,
                                     'from': from_url,
                                     'text': from_text})
        else:
            #填充有效链接数组 
            self.valid_url.append({'url': response.url,
                                   'from': from_url,
                                   'text': from_text})
            # 解析当前页面的域信息
            parsed_uri = urlparse(response.url)
            # 当以下情况解析新链接:
            #   - 如果当前页面不是外部域
            #   - 且其深度小于最大深度
            if parsed_uri.netloc == self.domain and depth < self.maxdepth:
                # 获得所有<a>标签
                a_selectors = response.xpath("//a")
                # 为每一个标签循环
                for selector in a_selectors:
                    # 解析链接文本
                    text = selector.xpath('text()').extract_first()
                    # 解析链接网址
                    link = selector.xpath('@href').extract_first()
                    # 创建新的Request请求对象
                    request = response.follow(link, callback=self.parse)
                    request.meta['from'] = response.url;
                    request.meta['text'] = text
                    # 利用生成器返回
                    yield request

监控正在运行的Spider程序

Scrapy在6023端口上提供telnet接口以监控正在运行的spider爬虫程序。telnet会话是一个您可以在其中执行Scrapy公有对象上的方法的Python shell脚本。

  1. 在后台运行你的spider爬虫: scrapy crawl link_checker -a url="http://www.linode.com" > 404.txt &
  2. 连接到telnet接口: telnet localhost 6023
  3. 打印Scrapy引擎状态的报告: est()
  4. 暂停爬取信息 engine.pause()
  5. 恢复爬取: engine.unpause()
  6. 停止爬取信息; engine.stop()

更多信息

有关此主题的其他信息,您可能需要参考以下资源。虽然我们希望提供的是有效资源,但请注意,我们无法保证外部托管材料的准确性或及时性。

  • Scrapy Project页面
  • 官方Scrapy文档
scrapyhtml数据挖掘scrapyhtml数据挖掘

0 人点赞