Scrapy框架(三) 基于Excel文件爬取的爬虫

引言

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


一、基于Excel文件爬取的爬虫

1.1 CSV文件创建

现在,创建一个.csv文件,其中包含想要抽取的信息。可以使用一个电子表格程序,比如Microsoft Excel,来创建这个.csv文件。

关于csv文件,可以参考我的一篇博客:用CSV模块读写CSV文件

按下图填入相关信息,参考如下:

然后将其命名为todo.csv,保存到爬虫根目录中。

1.2 Python3中的CSV模块

用CSV模块读写CSV文件

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

1
2
3
4
5
import csv
with open('name.csv','r') as fp:
reader = csv.DictReader(fp)
for line in reader:
print(line['id'],line['class'])

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

1.3 编辑爬虫

我们开始编辑爬虫。通过 scrapy genspider fromcsv 创建爬虫文件。我们将会用到.csv文件中的url,并且我们不希望受到域名的任何限制。因此,首先要做的事情就是移除start_urls以及allowed_domains,然后读取.csv文件。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# -*- coding: utf-8 -*-
import scrapy
import csv
from scrapy.http import Request
from scrapy.item import Item,Field
from scrapy.loader import ItemLoader

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

def start_requests(self):
path = 'C:\QzmVc1\Code\PyCharm\Python_Project\Python_Spider\Spider_Test1\CSV\\todo.csv'
with open(path,'r') as fp:
reader = csv.DictReader(fp)
for line in reader:
yield Request(line.pop('锘縰rl'),meta={'fields':line})

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


运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2018-11-19 12:20:44 [scrapy.core.engine] INFO: Spider opened
2018-11-19 12:20:44 [scrapy.extensions.logstats] INFO: Crawled 0 pages (at 0 pages/min), scraped 0 items (at 0 items/min)
2018-11-19 12:20:44 [scrapy.extensions.telnet] DEBUG: Telnet console listening on 127.0.0.1:6023
2018-11-19 12:20:44 [scrapy.core.engine] DEBUG: Crawled (404) <GET https://jcoffeezph.top/robots.txt> (referer: None)
2018-11-19 12:20:45 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://jcoffeezph.top/2018/11/14/JSP%E8%AE%BF%E9%97%AEmysql%E6%95%B0%E6%8D%AE%E5%BA%93/> (referer: None)
2018-11-19 12:20:45 [scrapy.core.scraper] DEBUG: Scraped from <200 https://jcoffeezph.top/2018/11/14/JSP%E8%AE%BF%E9%97%AEmysql%E6%95%B0%E6%8D%AE%E5%BA%93/>
{'time': ['2018-11-14'], 'title': ['\n JSP访问mysql数据库\n ']}
2018-11-19 12:20:46 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://jcoffeezph.top/2018/11/05/jsp%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95%E5%8F%8A%E5%86%85%E7%BD%AE%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D/> (referer: None)
2018-11-19 12:20:46 [scrapy.core.scraper] DEBUG: Scraped from <200 https://jcoffeezph.top/2018/11/05/jsp%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95%E5%8F%8A%E5%86%85%E7%BD%AE%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%AE%80%E5%8D%95%E4%BB%8B%E7%BB%8D/>
{'time': ['2018-11-05']}
2018-11-19 12:20:46 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://jcoffeezph.top/2018/11/07/%E6%95%A3%E5%88%97%E8%A1%A8%E7%AE%80%E6%9E%90/> (referer: None)
2018-11-19 12:20:46 [scrapy.core.scraper] DEBUG: Scraped from <200 https://jcoffeezph.top/2018/11/07/%E6%95%A3%E5%88%97%E8%A1%A8%E7%AE%80%E6%9E%90/>
{'time': ['2018-11-07']}
2018-11-19 12:20:46 [scrapy.core.engine] INFO: Closing spider (finished)
2018-11-19 12:20:46 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 1125,
'downloader/request_count': 4,
'downloader/request_method_count/GET': 4,
'downloader/response_bytes': 156880,
'downloader/response_count': 4,
'downloader/response_status_count/200': 3,
'downloader/response_status_count/404': 1,
'finish_reason': 'finished',
'finish_time': datetime.datetime(2018, 11, 19, 4, 20, 46, 842400),
'item_scraped_count': 3,
'log_count/DEBUG': 8,
'log_count/INFO': 7,
'response_received_count': 4,
'scheduler/dequeued': 3,
'scheduler/dequeued/memory': 3,
'scheduler/enqueued': 3,
'scheduler/enqueued/memory': 3,
'start_time': datetime.datetime(2018, 11, 19, 4, 20, 44, 97251)}
2018-11-19 12:20:46 [scrapy.core.engine] INFO: Spider closed (finished)

代码解释:

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

1
2
3
from scrapy.item import Item,Field
item = Item()
l = ItemLoader(item=item,response=response)

此外,我们还使用了Item的成员变量fields动态添加字段,这也是为什么我们没有导入CsvItem(items.py)而使用scrapy.item中的Item。为了能够动态添加新字段,并通过ItemLoader对其进行填充,需要实现的代码如下:

1
2
3
# 此处的 name 是一个字符串变量
item.fields[name] = Field()
l.add_xpath(name, xpath)

代码说明:

  1. reader是一个可迭代对象。
  2. line.pop(‘锘縰rl’) 乱码问题目前为止我还找不到方法,留坑…
  3. 关于Request中的参数meta:meta是一个字典,主要是用解析函数之间传递值,常见的情况是:在parse1中给item某些字段提取了值,但是另外一些值需要在parse2中提取,这时候需要将parse1中的item传到parse2方法中处理,显然无法直接给parse2设置额外参数。 Request对象接受一个meta参数,一个字典对象,同时response对象有一个meta属性可以获取到相应Request传过来的meta。