所在位置:

如何使用Python Scrapy开发你的第一个网页爬虫【翻译】

在本文中,我将写一个网页爬虫,它从OLX’s的 Electronics & Appliances 项目里爬取数据。但是在我写代码之前,这里先要简单介绍一下scrapy。

Scrapy 是什么?

来自 Wikipedia:

Scrapy是一个免费和开源的爬虫框架,用python编写的。它最初是为网页爬取的而设计的,也可以使用APIs来提取数据,或者用作通用网络爬取工具。目前由爬虫开发和服务公司的 Scrapinghub Ltd.来维护。

创建项目

Scrapy引入了一个项目中有多个爬虫或者蜘蛛的想法。这个概念非常有用,尤其是你在写网站的不同部分或者子域名的多个爬虫。因此,首先创建这个项目:

Adnans-MBP:ScrapyCrawlers AdnanAhmad$ scrapy startproject olx
New Scrapy project 'olx', using template directory '//anaconda/lib/python2.7/site-packages/scrapy/templates/project', created in:
    /Development/PetProjects/ScrapyCrawlers/olx

You can start your first spider with:
    cd olx
    scrapy genspider example example.com

创建你的爬虫

我运行 scrapy startproject olx 这个命令,它将用来创建名是olx的项目和并为你后续步骤提供帮助信息。你可以进入到新创建的文件夹,然后执行生成第一个蜘蛛的命令,这个蜘蛛包含了要爬取网站的域名和名字:

Adnans-MBP:ScrapyCrawlers AdnanAhmad$ cd olx/
Adnans-MBP:olx AdnanAhmad$ scrapy genspider electronics  www.olx.com.pk
Created spider 'electronics' using template 'basic' in module:
  olx.spiders.electronics

当我访问OLX的electronics部分,我用electronics的名字生成了第一个爬虫的代码。你可以根据需要来命名这只蜘蛛。最终项目的结构类似下面的例子:

如你所见,有一个独立的文件夹来放爬虫。你可以在一个项目里添加多个爬虫。让我们打开electronics.py爬虫文件。当你打开它,你将会看到以下内容:

# -*- coding: utf-8 -*-
import scrapy


class ElectronicsSpider(scrapy.Spider):
    name = "electronics"
    allowed_domains = ["www.olx.com.pk"]
    start_urls = ['http://www.olx.com.pk/']

    def parse(self, response):
        pass

如你所见,ElectronicsSpider是scrapy.Spider的子类。名字属性实际上是爬虫的名称,它是由蜘蛛生成命令中指定的。当运行这个爬虫的时候,这个名字将会有用的。allowed_domains 属性告诉你哪些域名能给爬虫访问和start_urls用来放提及初始化的URLs,这个urls是第一次需要被访问的。除了文件结构,这里好的特性用来创建爬虫的边界。

顾名思义,这个方法将会解释正在访问页面的内容。由于我想写一个爬取多页面的爬虫,因此我将会进行一些更改。

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

class ElectronicsSpider(CrawlSpider):
    name = "electronics"
    allowed_domains = ["www.olx.com.pk"]
    start_urls = [
        'https://www.olx.com.pk/computers-accessories/',
        'https://www.olx.com.pk/tv-video-audio/',
        'https://www.olx.com.pk/games-entertainment/'
    ]

    rules = (
        Rule(LinkExtractor(allow=(), restrict_css=('.pageNextPrev',)),
             callback="parse_item",
             follow=True),)

    def parse_item(self, response):
        print('Processing..' + response.url)

为了使爬虫爬取多个页面,我用crawler而不是scrapy.Spider来继承我的crawler,这个类可以更容易地爬取多个页面。你可以用这些生成的代码来执行相同的操作,但你需要递归操作才能浏览下一页

下一步设置变化的规则。这里有你提到的浏览网站的规则。LinkExtractor实际拿到参数来设置浏览的边界。这里我使用 restrict_css 参数来为下一页设置类。如果你进入到这页并检查元素,你将会看到以下内容:

pageNextPrev是一个用来获取下一页链接的类。call_back 参数告诉我们用什么方法访问这个页面元素。我们将会使用这个方法。

请记住,你需要把parse()方法更改为 parse_item(),或者你选择任何名称,以避免覆盖基类,否则即使您设置了follow=True,你的规则将不会生效。

到目前为止,一切都很好;让我们测试我们已经写好的爬虫。转到终端,在项目目录中输入:

scrapy crawl electronics

第三个参数实际上爬虫的名字,这个名字是我们最早设置的ElectronicsSpiders类的名字属性。打开终端,你将会发现很多对调试爬虫有用的信息。如果你不想看到这些调试信息,你可以禁止他们。这个命令将与--nolog开关相似。

scrapy crawl --nolog  electronics

如果你现在运行,将会打印下面的内容:

Adnans-MBP:olx AdnanAhmad$ scrapy crawl --nolog  electronics
Processing..https://www.olx.com.pk/computers-accessories/?page=2
Processing..https://www.olx.com.pk/tv-video-audio/?page=2
Processing..https://www.olx.com.pk/games-entertainment/?page=2
Processing..https://www.olx.com.pk/computers-accessories/
Processing..https://www.olx.com.pk/tv-video-audio/
Processing..https://www.olx.com.pk/games-entertainment/
Processing..https://www.olx.com.pk/computers-accessories/?page=3
Processing..https://www.olx.com.pk/tv-video-audio/?page=3
Processing..https://www.olx.com.pk/games-entertainment/?page=3
Processing..https://www.olx.com.pk/computers-accessories/?page=4
Processing..https://www.olx.com.pk/tv-video-audio/?page=4
Processing..https://www.olx.com.pk/games-entertainment/?page=4
Processing..https://www.olx.com.pk/computers-accessories/?page=5
Processing..https://www.olx.com.pk/tv-video-audio/?page=5
Processing..https://www.olx.com.pk/games-entertainment/?page=5
Processing..https://www.olx.com.pk/computers-accessories/?page=6
Processing..https://www.olx.com.pk/tv-video-audio/?page=6
Processing..https://www.olx.com.pk/games-entertainment/?page=6
Processing..https://www.olx.com.pk/computers-accessories/?page=7
Processing..https://www.olx.com.pk/tv-video-audio/?page=7
Processing..https://www.olx.com.pk/games-entertainment/?page=7

由于我设置了follow = True,因此爬虫将检查下一页的规则,并且将继续导航,除非它到达该规则所不符的页面,通常是列表的最后一页。

现在,想像你将使用本文提的方法来写相同的逻辑。第一,我要编写代码来生成多个进程。我还必须编写代码以不仅导航到下一页,而且还通过不访问不需要的URL来限制脚本保持在边界之内。Scrapy能减轻你的负担和把专注于主要逻辑,编写爬虫提取信息。

现在我将编写代码获取列表页中的单个项目链接。我将在我的 parse_item 方法中修改代码。

item_links = response.css('.large > .detailsLink::attr(href)').extract()
        for a in item_links:
            yield scrapy.Request(a, callback=self.parse_detail_page)

这里我用response的css来获取链接。正如我说的,你也可以使用 xpath 的,这取决于你。在这种情况下,这非常简单:

锚点链接具有detailsLink类。如果你仅仅使用response.css('.detailsLink'),就会得到单个条目的重复链接,由于img和h3标签有重复的链接。我还会利用父类的 large类来获取唯一的链接。我使用:: attr(href)提取链接本身的href部分。然后,我使用 extract() 方法。

使用这个方法的原因是.css和.xpath会返回SelectorList对象,extract()方法会返回实际的DOM以进行进一步处理。最后,我在scrapy.Request使用带有链接的callback。我没有检查Scrapy的内部代码,但很可能使用yiled来而不是return,因为你能产生多个条目。由于爬虫需要关注多个链接,因此yield就是最佳的选择。

顾名思义,这个 parse_detail_page 方法将会从详细页去解析各个信息。因此实际发生的是:

  • 你可以在 parse_item 里获得列表的条目。
  • 你可以在回调方法中传给它们以便进一步处理。

由于它只有两层遍历,因此我能用两种方法就能到达最低层。如果我是从OLX的主页开始爬取,我要写三个方法:前两个方法用来获取子类别和条目,最后一个用来解析实际的信息。理解了吗?

最后,我将解析这些实际的信息,该信息可以在像这样的一项中获得。

页面的解析信息没有什么不同,但是需要执行一些操作把解析的信息存储起来。我们需要为数据定义一个模型。我们需要告诉Scrapy我们想存储什么信息以备后用。让我们编辑items.py文件,这个文件是由Scrapy之前生成的。

import scrapy

class OlxItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    pass

OlxItem是我用来设置必填字段以保存信息的类。我会为我的模型类定义三个字段。

class OlxItem(scrapy.Item):
    # define the fields for your item here like:
    title = scrapy.Field()
    price = scrapy.Field()
    url = scrapy.Field()

我将存储文章的标题,价格和URL本身。

让我们回到crawler类和修改parsedetailpage方法。

现在,一种是开始编写代码,运行整个爬虫来进行测试,然后查看您是否步入正轨,但是Scrapy提供了另外一种很棒的工具。

Scrapy Shell

Scrapy Shell 是一个命令行工具,它可以让你不用运行整个爬虫就能测试解析代码。与爬虫访问所有的链接不同,Scrapy Sehll为提取数据保存单个页面的DOM元素。对我而言,我做了以下工作:

Adnans-MBP:olx AdnanAhmad$ scrapy shell https://www.olx.com.pk/item/asus-eee-pc-atom-dual-core-4cpus-beautiful-laptops-fresh-stock-IDUVo6B.html#4001329891

现在我不用重复去点击相同的URL就能简单地测试代码。我可以这样做来获取标题:

In [8]: response.css('h1::text').extract()[0].strip()
Out[8]: u"Asus Eee PC Atom Dual-Core 4CPU's Beautiful Laptops fresh Stock"

你在这里能找到熟悉的response.css。由于整个DOM都是可用的,你可以这样做。

然后我用下面的方法来获取价格:

In [11]: response.css('.pricelabel > strong::text').extract()[0]
Out[11]: u'Rs 10,500'

不需要做任何事来获取url,因为response.url就是返回当前访问过的URL。

现在已经检查了所有代码,是时候将其合并到parsedetailpage中了:

title = response.css('h1::text').extract()[0].strip()
price = response.css('.pricelabel > strong::text').extract()[0]
item = OlxItem()
item['title'] = title
item['price'] = price
item['url'] = response.url
yield item

当解析完所需要的信息,将会创建OlxItem实例和设置属性。现在是时候运行爬虫和存储信息了,在命令行中进行一些小修改:

scrapy crawl  electronics -o data.csv -t csv

我正在传递文件名和文件格式来保存数据。运行后,将会生成CSV。很简单,不是吗?与你自己编写的爬虫不同,你必须自己编写常规去存储数据。

可是等等!它并没有到此结束—您甚至可以获取JSON格式的数据;您要做的就是使用-t开关传递json。

Scrapy为您提供了另一个功能。在实际情况下,传递固定文件名没有任何意义。我如何拥有一些生成唯一文件名的功能?好吧,为此,您需要修改settings.py文件并添加以下两个条目:

FEED_URI = 'data/%(name)s/%(time)s.json'
FEED_FORMAT = 'json'

这里我将给出文件的模式,%(name)%是爬虫自己的名字,和time是时间戳。你可以在这里进一步了解它们。现在当我运行 scrapy crawl --nolog electronics 或者 scrapy crawl electronics,它将会在data目录生成一个JSON文件,像下面这样:

[
{"url": "https://www.olx.com.pk/item/acer-ultra-slim-gaming-laptop-with-amd-fx-processor-3gb-dedicated-IDUQ1k9.html", "price": "Rs 42,000", "title": "Acer Ultra Slim Gaming Laptop with AMD FX Processor 3GB Dedicated"},
{"url": "https://www.olx.com.pk/item/saw-machine-IDUYww5.html", "price": "Rs 80,000", "title": "Saw Machine"},
{"url": "https://www.olx.com.pk/item/laptop-hp-probook-6570b-core-i-5-3rd-gen-IDUYejF.html", "price": "Rs 22,000", "title": "Laptop HP Probook 6570b Core i 5 3rd Gen"},
{"url": "https://www.olx.com.pk/item/zong-4g-could-mifi-anlock-all-sim-supported-IDUYedh.html", "price": "Rs 4,000", "title": "Zong 4g could mifi anlock all Sim supported"},
...
]

编写爬虫是一个有趣的旅程,但是如果站点阻止了IP,您可能会碰壁。作为个人,您也负担不起昂贵的代理。 Scraper API为您提供了负担得起且易于使用的API,可让您轻松抓取网站。您无需担心被阻止,因为默认情况下,Scraper API使用代理访问网站。最重要的是,您也不必担心Selenium,因为Scraper API也提供了无头浏览器的功能。我还写了一篇有关如何使用它的文章。

单击此处用我的推荐链接进行注册,或输入促销码SCRAPE156980,您将获得10%的折扣。如果您没有获得折扣,请通过我的网站上的电子邮件告知我,我们一定会帮助您。

资源

GitHub repo for this article

原文

【上一篇】用scrapy爬取自己博客的文章

【下一篇】用scrapy爬取古诗词网站