5.4 基于 Excel 文件爬取的爬虫
大多数情况下,每个源网站只会有一个爬虫;不过在某些情况下,你想要抓取的数据来自多个网站,此时唯一变化的东西就是所使用的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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论