前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Scrapy从HTML标签中提取数据

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

作者头像
PantaZheng
发布2018-09-17 14:49:52
10.1K0
发布2018-09-17 14:49:52

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()

更多信息

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 准备工作
  • 安装Python 3环境
    • 在Debian 9系统上安装
      • 在CentOS 7系统下安装
      • 安装Scrapy
        • 系统级别下安装(不推荐)
          • 在虚拟环境下安装Scrapy
          • 创建Scrapy项目
          • 开启Spider爬虫程序
          • 使用Scrapy Shell
          • 编写爬虫爬取逻辑
            • 添加Request请求的元信息
              • 设置需处理的HTTP状态
                • 设置信息处理程序
                  • 命令行的输入起始URL网址
                  • 进行项目设置
                  • 移除域名限制
                  • 完全实现的Spider爬虫程序
                  • 监控正在运行的Spider程序
                  • 更多信息
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档