返回介绍

3.3 一个 Scrapy 项目

发布于 2024-01-30 22:48:37 字数 20747 浏览 0 评论 0 收藏 0

到目前为止,我们只是在通过scrapy shell“小打小闹”。现在,既然已经拥有了用于开始第一个Scrapy项目的所有必要组成部分,那么让我们按下Ctrl + D 退出scrapy shell吧。需要注意的是,你现在输入的所有内容都将丢失。显然,我们并不希望在每次爬取某些东西的时候都要输入代码,因此一定要谨记scrapy shell只是一个可以帮助我们调试页面、XPath表达式和Scrapy对象的工具。不要花费大量时间在这里编写复杂代码,因为一旦你退出,这些代码就都会丢失。为了编写真实的Scrapy代码,我们将使用项目。下面创建一个Scrapy项目,并将其命名为"properties",因为我们正在抓取的数据是房产。

$ scrapy startproject properties
$ cd properties
$ tree
.
├── properties
│  ├── __init__.py
│  ├── items.py
│  ├── pipelines.py
│  ├── settings.py
│  └── spiders
│     └── __init__.py
└── scrapy.cfg

2 directories, 6 files

提醒一下,你可以从GitHub中获得本书的全部源代码。要下载该代码,可以使用如下命令:

git clone https://github.com/scalingexcellence/ scrapybook

本章的代码在ch03目录中,其中该示例的代码在ch03/properties目录中。

我们可以看到这个Scrapy项目的目录结构。命令scrapy startproject properties创建了一个以项目名命名的目录,其中包含3个我们感兴趣的文件,分别是items.py、pipelines.py和settings.py。这里还有一个名为spiders的子目录,目前为止该目录是空的。在本章中,我们将主要在items.py文件和spiders目录中工作。在后续的章节里,还将对设置、管道和scrapy.cfg文件有更多探索。

3.3.1 声明item

我们使用一个文件编辑器打开items.py文件。现在该文件中已经包含了一些模板代码,不过还需要针对用例对其进行修改。我们将重定义PropertiesItem类,添加表3.2中总结出来的字段。

我们还会添加几个字段,我们的应用在后续会用到这些字段(这样之后就不需要再修改这个文件了)。本书后续的内容会深入解释它们。需要重点注意的一个事情是,我们声明一个字段并不意味着我们将在每个爬虫中都填充该字段,或是全部使用它。你可以随意添加任何你感觉合适的字段,因为你可以在之后更正它们。

表3.2

可计算字段

Python表达式

images

图像管道将会基于image_urls自动填充该字段。可以在后续的章节中了解更多相关内容

location

我们的地理编码管道将会在后面填充该字段。可以在后续的章节中了解更多相关的内容

我们还会添加一些管理字段(见表3.3)。这些字段不是特定于某个应用程序的,而是我个人感兴趣的字段,可能会在未来帮助我调试爬虫。你可以在项目中选择其中的一些字段,当然也可以不选择。如果你仔细观察这些字段,就会明白它们可以让我清楚何地(server、url)、何时(date)、如何(spider)执行的抓取。它们还可以自动完成一些任务,比如使item失效、规划新的抓取迭代或是删除来自有问题的爬虫的item。如果你还不能理解所有的表达式,尤其是server的表达式,也不用担心。当我们进入到后面的章节时,这些都会变得越来越清楚。

表3.3

管理字段

Python表达式

url

response.url
示例值:'http://web.../property_000000. html'

project

self.settings.get('BOT_NAME')
示例值:'properties'

spider

self.name
示例值:'basic'

server

socket.gethostname()
示例值:'scrapyserver1'

date

datetime.datetime.now()
示例值:datetime.datetime(2015, 6, 25...)

给出字段列表之后,再去修改并自定义scrapy startproject为我们创建的PropertiesItem类,就会变得很容易。在文本编辑器中,修改properties/items.py文件,使其包含如下内容:

from scrapy.item import Item, Field

class PropertiesItem(Item):
  # Primary fields
  title = Field()
  price = Field()
  description = Field()
  address = Field()
  image_urls = Field()

  # Calculated fields
  images = Field()
  location = Field()

  # Housekeeping fields
  url = Field()
  project = Field()
  spider = Field()
  server = Field()
  date = Field()

由于这实际上是我们在文件中编写的第一个Python代码,因此需要重点指出的是,Python使用缩进作为其语法的一部分。在每个字段的起始部分,会有精确的4个空格或1个制表符,这一点非常重要。如果你在其中一行使用了4个空格,而在另一行使用了3个空格,就会出现语法错误。如果你在其中一行使用了4个空格,而在另一行使用了制表符,同样也会产生语法错误。这些空格在PropertiesItem类下,将字段声明组织到了一起。其他语言一般使用大括号({})或特殊的关键词(如begin-end)来组织代码,而Python使用空格。

3.3.2 编写爬虫

我们已经在半路上了。现在,我们需要编写爬虫。通常,我们会为每个网站或网站的一部分(如果网站非常大的话)创建一个爬虫。爬虫代码实现了完整的UR2IM流程,我们很快就可以看到。

什么时候使用爬虫,什么时候使用项目呢?项目是由Item和若干爬虫组成的。如果有很多网站,并且需要从中抽取相同类型的Item,比如:房产,那么所有这些网站都可以使用同一个项目,并且为每个源/网站使用一个爬虫。反之,如果要处理图书及房产这两种不同的源时,则应该使用不同的项目。

当然,可以在文本编辑器中从头开始创建一个爬虫,不过为了减少一些输入,更好的方法是使用scrapy genspider命令,如下所示。

$ scrapy genspider basic web
Created spider 'basic' using template 'basic' in module:
 properties.spiders.basic

现在,如果再次运行tree命令,就会注意到与之前相比唯一的不同是在properties/spiders目录中增加了一个新文件basic.py。前面的命令所做的工作就是创建了一个名为"basic"的“默认”爬虫,并且该爬虫被限制为只能爬取web域名下的URL。如果需要的话,可以很容易地移除这个限制,不过目前来说没有问题。爬虫使用"basic"模板创建。你可以通过输入scrapy genspider-l来查看其他可用的模板,然后在执行scrapy genspider时,通过-t参数,使用任意其他模板创建爬虫。在本章稍后的部分,我们将会看到一个示例。

Scrapy有许多子目录。我们一般假设你位于包含scrapy.cfg文件的目录中。这是项目的“顶级”目录。现在,每当我们引用Python“包”和“模块”时,它们就是以映射目录结构的方式设置的。比如,输出提到了properties.spiders.basic,就是指properties/spiders目录中的basic.py文件。我们早前定义的PropertiesItem类是在properties.items模块中,该模块对应的就是properties目录中的items.py文件。

如果查看properties/spiders/basic.py文件,可以看到如下代码。

import scrapy

class BasicSpider(scrapy.Spider):
  name = "basic"
  allowed_domains = ["web"]
  start_urls = (
    'http://www.web/',
  )

  def parse(self, response):
    pass

import语句能够让我们使用Scrapy框架中已有的类。下面是扩展自scrapy.Spider的BasicSpider类的定义。通过“扩展”的方式,尽管我们实际上没有写任何代码,但是该类已经“继承”了Scrapy框架中的Spider类的相当一部分功能。这样,就可以只额外编写少量的代码行,而获得一个完整运行的爬虫了。然后,我们可以看到一些爬虫的参数,比如它的名字以及我们允许其爬取的域名。最后是空函数parse()的定义,该函数包含了两个参数,分别是self和response对象。通过使用self引用,我们就可以使用爬虫中感兴趣的功能了。而另一个对象response,我们应该很熟悉,它就是我们在scrapy shell中使用过的response对象。

这是你的代码——你的爬虫。不要害怕修改它,你不会真的把事情搞砸的。即使在最坏的情况下,你还可以使用rmproperties/spiders/basic.py*删除文件,然后再重新生成。尽情发挥吧!

好了,让我们开始改造吧。首先,要使用在scrapy shell中使用过的那个URL,对应地设置到start_urls参数中。然后,将使用爬虫预定义的方法log(),输出在基本字段表中总结的所有内容。修改后,properties/spiders/basic.py的代码如下所示。

import scrapy

class BasicSpider(scrapy.Spider):
  name = "basic"
  allowed_domains = ["web"]
  start_urls = (
    'http://web:9312/properties/property_000000.html',
  )

  def parse(self, response):
    self.log("title: %s" % response.xpath(
      '//*[@itemprop="name"][1]/text()').extract())
    self.log("price: %s" % response.xpath(
      '//*[@itemprop="price"][1]/text()').re('[.0-9]+'))
    self.log("description: %s" % response.xpath(
      '//*[@itemprop="description"][1]/text()').extract())
    self.log("address: %s" % response.xpath(
      '//*[@itemtype="http://schema.org/'
      'Place"][1]/text()').extract())
    self.log("image_urls: %s" % response.xpath(
      '//*[@itemprop="image"][1]/@src').extract())

我将会不时地修改格式,以便在屏幕和纸张中都能很好地显示。这并不意味着它有什么特殊的含义。

等了这么久,终于到了运行爬虫的时候了。我们可以使用命令scrapy crawl以及爬虫的名称来运行爬虫。

$ scrapy crawl basic
INFO: Scrapy 1.0.3 started (bot: properties)
...
INFO: Spider opened
DEBUG: Crawled (200) <GET http://...000.html>
DEBUG: title: [u'set unique family well']
DEBUG: price: [u'334.39']
DEBUG: description: [u'website...']
DEBUG: address: [u'Angel, London']
DEBUG: image_urls: [u'../images/i01.jpg']
INFO: Closing spider (finished)
...

非常好!不要被大量的日志行吓倒。我们将会在后续的章节中更详细地研究其中的一部分,不过对于现在而言,只需要注意到所有使用XPath表达式收集到的数据确实能够通过这个简单的爬虫代码抽取出来就可以了。

让我们再来试验一下另一个命令:scrapy parse。它允许我们使用“最合适”的爬虫来解析参数中给定的任意URL。我不喜欢抱有侥幸心理,所以我们使用它结合--spider参数来设置爬虫。

$ scrapy parse --spider=basic http://web:9312/properties/property_000001.
html

你会看到输出和之前是相似的,只不过现在是另一套房产。

scrapy parse同样也是一个相当方便的调试工具。在任何情况下,如果你想“认真”抓取的话,应当使用主命令scrapy crawl。

3.3.3 填充item

我们将会对前面的代码进行少量修改,以填充PropertiesItem。你将会看到,尽管修改非常轻微,但是会“解锁”大量的新功能。

首先,需要引入PropertiesItem类。如前所述,它在properties目录的items.py文件中,也就是properties.items模块中。我们回到properties/spiders/basic.py文件,使用如下命令引入该模块。

from properties.items import PropertiesItem

然后需要进行实例化,并返回一个对象。这非常简单。在parse()方法中,可以通过添加item = PropertiesItem()语句创建一个新的item,然后可以按如下方式为其字段分配表达式。

item['title'] =
response.xpath('//*[@itemprop="name"][1]/text()').extract()

最后,使用return item返回item。最新版的properties/spiders/basic.py代码如下所示。

import scrapy
from properties.items import PropertiesItem

class BasicSpider(scrapy.Spider):
  name = "basic"
  allowed_domains = ["web"]
  start_urls = (
    'http://web:9312/properties/property_000000.html',
  )

  def parse(self, response):
    item = PropertiesItem()
    item['title'] = response.xpath(
      '//*[@itemprop="name"][1]/text()').extract()
    item['price'] = response.xpath(
      '//*[@itemprop="price"][1]/text()').re('[.0-9]+')
    item['description'] = response.xpath(
      '//*[@itemprop="description"][1]/text()').extract()
    item['address'] = response.xpath(
      '//*[@itemtype="http://schema.org/'
      'Place"][1]/text()').extract()
    item['image_urls'] = response.xpath(
      '//*[@itemprop="image"][1]/@src').extract()
    return item

现在,如果你再像之前那样运行scrapy crawl basic,就会发现一个非常小但很重要的区别。我们不再在日志中记录抓取值(所以没有包含字段值的DEBUG:行了),而是看到如下的输出行。

DEBUG: Scraped from <200
http://...000.html>
 {'address': [u'Angel, London'],
  'description': [u'website ... offered'],
  'image_urls': [u'../images/i01.jpg'],
  'price': [u'334.39'],
  'title': [u'set unique family well']}

这是从本页面抓取得到的PropertiesItem。非常好,因为Scrapy是围绕着Items的概念构建的,也就是说你现在可以使用后续章节中介绍的管道,对其进行过滤和丰富了,并且可以通过“Feed exports”将其以不同的格式导出存储到不同的地方。

3.3.4 保存文件

请尝试如下爬取示例。

$ scrapy crawl basic -o items.json
$ cat items.json
[{"price": ["334.39"], "address": ["Angel, London"], "description": 
["website court ... offered"], "image_urls": ["../images/i01.jpg"], 
"title": ["set unique family well"]}]

$ scrapy crawl basic -o items.jl
$ cat items.jl
{"price": ["334.39"], "address": ["Angel, London"], "description": 
["website court ... offered"], "image_urls": ["../images/i01.jpg"], 
"title": ["set unique family well"]}

$ scrapy crawl basic -o items.csv
$ cat items.csv
description,title,url,price,spider,image_urls...
"...offered",set unique family well,,334.39,,../images/i01.jpg
$ scrapy crawl basic -o items.xml
$ cat items.xml
<?xml version="1.0" encoding="utf-8"?>
<items><item><price><value>334.39</value></price>...</item></items>

我们不需要编写任何额外的代码,就可以保存为这些不同的格式。Scrapy在幕后识别你想要输出的文件扩展名,并以适当的格式输出到文件中。前面的格式覆盖了一些最常见的用例。CSV和XML文件非常流行,因为类似微软Excel的电子表格程序可以直接打开它们。JSON文件在网上非常流行,原因是它们富有表现力而且与JavaScript的关系相当密切。JSON与JSON行(JSON Line)格式的轻微不同是,.json文件是在一个大数组中存储JSON对象的。这就意味着如果你有一个1GB的文件,你可能不得不在使用典型的解析器解析之前,将其全部存入内存当中。而.jl文件则是每行包含一个JSON对象,所以它们可以被更高效地读取。

将你生成的文件保存到文件系统之外的地方也很容易。比如,通过使用如下命令,Scrapy可以自动将文件上传到FTP或S3存储桶中。

$ scrapy crawl basic -o "ftp://user:pass@ftp.scrapybook.com/items.json "
$ scrapy crawl basic -o "s3://aws_key:aws_secret@scrapybook/items.json"

需要注意的是,除非凭证和URL都更新为与有效的主机/S3提供商相匹配,否则该示例无法工作。

我的MySQL驱动在哪里?起初,我也对Scrapy缺少针对MySQL或其他数据库的内置支持感到惊讶。而实际上,没有什么是内置的,这与Scrapy的思考方式是完全违背的。Scrapy的目标是快速和可扩展。它使用了很少的CPU,以及尽可能高的入站带宽。从性能的角度来看,将数据插入到大部分关系型数据库将会是一场灾难。当需要将item插入到数据库时,必须将其先存储到文件当中,然后再使用批量加载机制导入它们。在第9章中,我们将会看到多种高效的方式,用来将独立的item导入到数据库中。

这里需要注意的另一件事是,如果你现在尝试使用scrapy parse,它会向你显示已经抓取的item,以及你的爬取生成的新请求(本例中没有)。

$ scrapy parse --spider=basic http://web:9312/properties/property_000001.
html
INFO: Scrapy 1.0.3 started (bot: properties)
...
INFO: Spider closed (finished)

>>> STATUS DEPTH LEVEL 1 <<<
# Scraped Items ------------------------------------------------
[{'address': [u'Plaistow, London'],
 'description': [u'features'],
 'image_urls': [u'../images/i02.jpg'],
 'price': [u'388.03'],
 'title': [u'belsize marylebone...deal']}]
# Requests ------------------------------------------------
[]

在调试给出意料之外的结果的URL时,你会更加感激scrapy parse。

3.3.5 清理——item装载器与管理字段

恭喜,你在创建基础爬虫方面做得不错!下面让我们做得更专业一些吧。

首先,我们使用一个强大的工具类——ItemLoader,以替代那些杂乱的extract()和xpath()操作。通过使用该类,我们的parse()方法会按如下进行代码变更。

def parse(self, response):
  l = ItemLoader(item=PropertiesItem(), response=response)

  l.add_xpath('title', '//*[@itemprop="name"][1]/text()')
  l.add_xpath('price', './/*[@itemprop="price"]'
      '[1]/text()', re='[,.0-9]+')
  l.add_xpath('description', '//*[@itemprop="description"]'
      '[1]/text()')
  l.add_xpath('address', '//*[@itemtype='
      '"http://schema.org/Place"][1]/text()')
  l.add_xpath('image_urls', '//*[@itemprop="image"][1]/@src')

  return l.load_item()

好多了,是不是?不过,这种写法并不只是在视觉上更加舒适,它还非常明确地声明了我们意图去做的事情,而不会将其与实现细节混淆起来。这就使得代码具有更好的可维护性以及自描述性。

ItemLoader提供了许多有趣的结合数据及对数据进行格式化和清洗的方式。请注意,此类功能的开发非常活跃,因此请查阅Scrapy优秀的官方文档来发现使用它们的更高效的方式,文档地址为 http://doc.scrapy.org/en/latest/topics/loaders.html。Itemloaders通过不同的处理类传递XPath/CSS表达式的值。处理器是一个快速而又简单的函数。处理器的一个例子是Join()。假设你已经使用类似//p 的XPath表达式选取了很多个段落,该处理器可以将这些段落结合成一个条目。另一个非常有意思的处理器是MapCompose()。通过该处理器,你可以使用任意Python函数或Python函数链,以实现复杂的功能。比如,MapCompose(float)可以将字符串数据转换为数值,而MapCompose(Unicode.strip, Unicode.title)可以删除多余的空白符,并将字符串格式化为每个单词均为首字母大写的样式。让我们看一些处理器的例子,如表3.4所示。

表3.4

处 理 器

功  能

Join()

把多个结果连接在一起

MapCompose(unicode.strip)

去除首尾的空白符

MapCompose(unicode.strip,
unicode. title)

与MapCompose(unicode.strip)相同,不过还会使结果按照标题格式

MapCompose(float)

将字符串转为数值

MapCompose(lambda i: i.replace
(',', ''), float)

将字符串转为数值,并忽略可能存在的','字符

MapCompose(lambda i: urlparse.
urljoin (response.url, i))

以response.url为基础,将URL相对路径转换为URL绝对路径

你可以使用任何Python表达式作为处理器。可以看到,我们可以很容易地将它们一个接一个地连接起来,比如,我们前面给出的去除首尾空白符以及标题化的例子。unicode.strip()和unicode.title()在某种意义上来说比较简单,它们只有一个参数,并且也只有一个返回结果。我们可以在MapCompose处理器中直接使用它们。而另一些函数,像replace()或urljoin(),就会稍微有点复杂,它们需要多个参数。对于这种情况,我们可以使用Python的“lambda表达式”。lambda表达式是一种简洁的函数。比如下面这个简洁的lambda表达式。

myFunction = lambda i: i.replace(',', '')

可以代替:

def myFunction(i):
  return i.replace(',', '')

通过使用lambda,我们将类似replace()和urljoin()这样的函数包装在只有一个参数及一个返回结果的函数中。为了能够更好地理解表3.4中的处理器,下面看几个使用处理器的例子。使用scrapy shell打开任意URL,然后尝试如下操作。

>>> from scrapy.loader.processors import MapCompose, Join
>>> Join()(['hi','John'])
u'hi John'
>>> MapCompose(unicode.strip)([u' I',u' am\n'])
[u'I', u'am']
>>> MapCompose(unicode.strip, unicode.title)([u'nIce cODe'])
[u'Nice Code']
>>> MapCompose(float)(['3.14'])
[3.14]
>>> MapCompose(lambda i: i.replace(',', ''), float)(['1,400.23'])
[1400.23]
>>> import urlparse
>>> mc = MapCompose(lambda i: urlparse.urljoin('http://my.com/test/abc', 
i))
>>> mc(['example.html#check'])
['http://my.com/test/example.html#check']
>>> mc(['http://absolute/url#help'])
['http://absolute/url#help']

这里要解决的关键问题是,处理器只是一些简单小巧的功能,用来对我们的XPath/CSS结果进行后置处理。现在,在爬虫中使用几个这样的处理器,并按照我们想要的方式输出。

def parse(self, response):
  l.add_xpath('title', '//*[@itemprop="name"][1]/text()',
        MapCompose(unicode.strip, unicode.title))
  l.add_xpath('price', './/*[@itemprop="price"][1]/text()',
        MapCompose(lambda i: i.replace(',', ''), float),
        re='[,.0-9]+')
  l.add_xpath('description', '//*[@itemprop="description"]'
        '[1]/text()', MapCompose(unicode.strip), Join())
  l.add_xpath('address',
        '//*[@itemtype="http://schema.org/Place"][1]/text()',
        MapCompose(unicode.strip))
  l.add_xpath('image_urls', '//*[@itemprop="image"][1]/@src',
        MapCompose(
        lambda i: urlparse.urljoin(response.url, i)))

完整列表将会在本章后续部分给出。当你使用我们目前开发的代码运行scrapy crawl basic时,可以得到更加整洁的输出值。

'price': [334.39],
'title': [u'Set Unique Family Well']

最后,我们可以通过使用add_value()方法,添加Python计算得出的单个值(而不是XPath/CSS表达式)。我们可以用该方法设置“管理字段”,比如URL、爬虫名称、时间戳等。我们还可以直接使用管理字段表中总结出来的表达式,如下所示。

l.add_value('url', response.url)
l.add_value('project', self.settings.get('BOT_NAME'))
l.add_value('spider', self.name)
l.add_value('server', socket.gethostname())
l.add_value('date', datetime.datetime.now())

为了能够使用其中的某些函数,请记得引入datetime和socket模块。

好了!我们现在已经得到了非常不错的Item。此刻,你的第一感觉可能是所做的这些都很复杂,你可能想要知道这些工作是不是值得付出努力。答案当然是值得的——这是因为,这就是你为了从页面抽取数据并将其存储到Item中几乎所有需要知道的东西。如果你从零开始编写,或者使用其他语言,该代码通常都会非常难看,而且很快就会变得不可维护。而使用Scrapy时,只需要仅仅25行代码。该代码十分简洁,用于表明意图,而不是实现细节。你清楚地知道每一行代码都在做什么,并且它可以很容易地修改、复用及维护。

你可能产生的另一个感觉是所有的处理器以及ItemLoader并不值得去努力。如果你是一个经验丰富的Python开发者,可能会觉得有些不舒服,因为你必须去学习新的类,来实现通常使用字符串操作、lambda表达式以及列表推导式就可以完成的操作。不过,这只是ItemLoader及其功能的简要概述。如果你更加深入地了解它,就不会再回头了。ItemLoader和处理器是基于编写并支持了成千上万个爬虫的人们的抓取需求而开发的工具包。如果你准备开发多个爬虫的话,就非常值得去学习使用它们。

3.3.6 创建contract

contract有点像为爬虫设计的单元测试。它可以让你快速知道哪里有运行异常。例如,假设你在几个星期之前编写了一个抓取程序,其中包含几个爬虫,今天想要检查一下这些爬虫是否仍然能够正常工作,就可以使用这种方式。contract包含在紧挨着函数名的注释(即文档字符串)中,并且以@开头。下面来看几个contract的例子。

def parse(self, response):
  """ This function parses a property page.

  @url http://web:9312/properties/property_000000.html
  @returns items 1
  @scrapes title price description address image_urls
  @scrapes url project spider server date
  """

上述代码的含义是,检查该URL,并找到我列出的字段中有值的一个Item。现在,当你运行scrapy check时,就会去检查contract是否能够满足。

$ scrapy check basic
----------------------------------------------------------------
Ran 3 contracts in 1.640s
OK

如果将url字段留空(通过注释掉该行来设置),你会得到一个失败描述。

FAIL: [basic] parse (@scrapes post-hook)
------------------------------------------------------------------
ContractFail: 'url' field is missing

contract失败的原因可能是爬虫代码无法运行,或者是你要检查的URL的XPath表达式已经过时了。虽然结果并不详尽,但它是抵御坏代码的第一道灵巧的防线。

综合上面的内容,下面给出我们的第一个基础爬虫的代码。

from scrapy.loader.processors import MapCompose, Join
from scrapy.loader import ItemLoader
from properties.items import PropertiesItem
import datetime
import urlparse
import socket
import scrapy

class BasicSpider(scrapy.Spider):
  name = "basic"
  allowed_domains = ["web"]

  # Start on a property page
  start_urls = (
    'http://web:9312/properties/property_000000.html',
  )

  def parse(self, response):
    """ This function parses a property page.
    @url http://web:9312/properties/property_000000.html
    @returns items 1
    @scrapes title price description address image_urls
    @scrapes url project spider server date
    """
    # Create the loader using the response
    l = ItemLoader(item=PropertiesItem(), response=response)

    # Load fields using XPath expressions
    l.add_xpath('title', '//*[@itemprop="name"][1]/text()',
          MapCompose(unicode.strip, unicode.title))
    l.add_xpath('price', './/*[@itemprop="price"][1]/text()',
          MapCompose(lambda i: i.replace(',', ''),
          float),
          re='[,.0-9]+')
    l.add_xpath('description', '//*[@itemprop="description"]'
          '[1]/text()',
          MapCompose(unicode.strip), Join())
    l.add_xpath('address',
          '//*[@itemtype="http://schema.org/Place"]'
          '[1]/text()',
          MapCompose(unicode.strip))
    l.add_xpath('image_urls', '//*[@itemprop="image"]'
          '[1]/@src', MapCompose(
          lambda i: urlparse.urljoin(response.url, i)))

    # Housekeeping fields
    l.add_value('url', response.url)
    l.add_value('project', self.settings.get('BOT_NAME'))
    l.add_value('spider', self.name)
    l.add_value('server', socket.gethostname())
    l.add_value('date', datetime.datetime.now())

    return l.load_item()

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文