返回介绍

5.4 基于 Excel 文件爬取的爬虫

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

大多数情况下,每个源网站只会有一个爬虫;不过在某些情况下,你想要抓取的数据来自多个网站,此时唯一变化的东西就是所使用的XPath表达式。对于此类情况,如果为每个网站都使用一个爬虫则显得有些小题大做。那么可以只使用一个爬虫来爬取所有这些网站吗?答案是肯定的。

让我们为该实验创建一个新的爬虫,因为这次爬取的条目会和之前区别很大(实际上我们还没有在该项目中定义任何东西!)。假设此时在ch05下的properties目录中。让我们向上一层,如下面的代码所示进行操作。

$ pwd
/root/book/ch05/properties
$ cd ..
$ pwd
/root/book/ch05

我们创建了一个名为generic的新项目,以及一个名为fromcsv的爬虫。

$ scrapy startproject generic
$ cd generic
$ scrapy genspider fromcsv example.com

现在,创建一个.csv文件,其中包含想要抽取的信息。可以使用一个电子表格程序,比如Microsoft Excel,来创建这个.csv文件。填入如图5.5所示的几个URL和XPath表达式,然后将其命名为todo.csv,保存到爬虫目录当中(scrapy.cfg所在目录)。要想保存为.csv文件,需要在保存对话框中选择CSV文件(Windows)作为文件格式。

图5.5 包含URL和XPath表达式的todo.csv

很好!如果一切都已就绪,你就可以在终端上看到该文件。

$ cat todo.csv
url,name,price
a.html,"//*[@id=""itemTitle""]/text()","//*[@id=""prcIsum""]/text()"
b.html,//h1/text(),//span/strong/text()
c.html,"//*[@id=""product-desc""]/span/text()"

Python有一个用于处理.csv文件的内置库。只需通过import csv导入模块,然后就可以使用如下这些直截了当的代码,以字典的形式读取文件中的所有行了。在当前目录下打开Python提示符,就可以尝试如下代码。

$ pwd
/root/book/ch05/generic2
$ python
>>> import csv
>>> with open("todo.csv", "rU") as f:
    reader = csv.DictReader(f)
    for line in reader:
      print line

文件中的第一行会被自动作为标题行处理,并且会根据它们得出字典中键的名称。在接下来的每一行中,会得到一个包含行内数据的字典。我们使用for循环迭代每一行。当运行前面的代码时,可以得到如下输出。

{'url': ' http://a.html', 'price': '//*[@id="prcIsum"]/text()', 
'name': '//*[@id="itemTitle"]/text()'}
{'url': ' http://b.html', 'price': '//span/strong/text()', 'name': '//
h1/text()'}
{'url': ' http://c.html', 'price': '', 'name': '//*[@id="product-
desc"]/span/text()'}

非常好。现在,可以编辑generic/spiders/fromcsv.py这个爬虫了。我们将会用到.csv文件中的URL,并且不希望有任何域名限制。因此,首先要做的事情就是移除start_urls以及allowed_domains,然后读取.csv文件。

由于我们事先并不知道想要起始的URL,而是从文件中读取得到的,因此需要实现一个start_requests()方法。对于每一行,创建Request,然后对其进行yield操作。此外,还会在reqeust.meta中存储来自csv文件的字段名称和XPath表达式,以便在parse()函数中使用它们。然后,使用Item和ItemLoader填充Item的字段。下面是完整的代码。

import csv
import scrapy
from scrapy.http import Request
from scrapy.loader import ItemLoader
from scrapy.item import Item, Field

class FromcsvSpider(scrapy.Spider):
  name = "fromcsv"

def start_requests(self):
  with open("todo.csv", "rU") as f:
    reader = csv.DictReader(f)
    for line in reader:
      request = Request(line.pop('url'))
      request.meta['fields'] = line
      yield request

def parse(self, response):
  item = Item()
  l = ItemLoader(item=item, response=response)
  for name, xpath in response.meta['fields'].iteritems():
    if xpath:
    item.fields[name] = Field()
    l.add_xpath(name, xpath)
return l.load_item()

接下来开始爬取,并将结果输出到out.csv文件中。

$ scrapy crawl fromcsv -o out.csv
INFO: Scrapy 0.0.3 started (bot: generic)
...
DEBUG: Scraped from <200 a.html>
{'name': [u'My item'], 'price': [u'128']}
DEBUG: Scraped from <200 b.html>
{'name': [u'Getting interesting'], 'price': [u'300']}
DEBUG: Scraped from <200 c.html>
{'name': [u'Buy this now']}
...
INFO: Spider closed (finished)
$ cat out.csv
price,name
128,My item
300,Getting interesting
,Buy this now

正如爬取得到的结果一样,非常简洁直接!

在代码中,你可能已经注意到了几个事情。由于我们没有为该项目定义系统范围的Item,因此必须像如下代码这样手动为ItemLoader提供。

item = Item()
l = ItemLoader(item=item, response=response)

此外,我们还使用了Item的成员变量fields动态添加字段。为了能够动态添加新字段,并通过ItemLoader对其进行填充,需要实现的代码如下。

item.fields[name] = Field()
l.add_xpath(name, xpath)

最后,还可以使代码更加好看。硬编码todo.csv文件名不是一个非常好的实践。Scrapy提供了一个非常便捷的方法,用于传输参数到爬虫当中。当传输一个命令行参数-a时(比如:-a variable=value),就会为我们设置一个爬虫属性,并且可以通过self.variable取得该值。为了检查变量,并在未提供该变量时使用默认值,可以使用Python的getattr()方法:getattr(self, 'variable', 'default')。总之,我们将原来的with open…语句替换为如下语句。

with open(getattr(self, "file", "todo.csv"), "rU") as f:

现在,除非明确使用-a参数设置源文件名,否则将会使用todo.csv作为其默认值。当给出另一个文件another_todo.csv时,可以按如下方式运行。

$ scrapy crawl fromcsv -a file=another_todo.csv -o out.csv

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

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

发布评论

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