Python 下用 Scrapy 采集知网期刊数据(二)
需求
本次采集需要采集期刊 2000~2010 年间的数据,相关的数据首先是期刊中各文献的数据:
主要包括 title(篇名)
,author(作者)
,journalName(刊名)
,publicationDate(发表时间)
,referenceNum(被引数)
以及 downloadNum
下载数
其次是每篇文献的引证文献数据:
主要包括 paper_title(篇名)
,paper_author(作者)
,paper_journalName(刊名)
,paper_time(发表时间)
以及quote_title(引用的文献篇名)
。
搜索期刊
抓包分析 在采集数据之前首先需要获取到期刊的搜索结果,而本次采集主要和下列的搜索参数相关:
- 起始年份
- 模糊或精确搜索
- 期刊名称 抓包之后发现搜索过程主要和下图标有红线的两个请求相关,第一个请求用于得到搜索结果,第二个请求则是将搜索的结果显示出来。 对第一个请求进行分析之后发现了与搜索条件相对应的搜索参数。 第二个请求大部分是固定参数值
- 发送请求
从 Excel 读取参数: 由于需要采集每本期刊 11 年的数据,所以为了减少搜索结果从而尽可能避免需要输入验证码,所以将所有的搜索条件保存在一个 Excel 文件里面: 读取参数过程如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def read_excel(self):
# 打开文件
workbook = xlrd.open_workbook(r'/Users/firstdream/PycharmProjects/cnkiSprider/查询条件.xlsx')
# 获取所有sheet
# 根据sheet索引或者名称获取sheet内容
sheet1 = workbook.sheet_by_index(0) # sheet索引从0开始
search_setting = []
for row in range(sheet1.nrows):
setting = ""
for col in range(sheet1.ncols):
if isinstance(sheet1.row(row)[col].value, float):
setting = setting + str(int(sheet1.row(row)[col].value)) + ","
else:
setting = setting + str(sheet1.row(row)[col].value)
search_setting.append(setting)
return search_setting将每行的搜索条件拼接起来并保存在一个 list 当中。
生成请求 由于请求的参数较多,所以采用词典进行请求的参数配置,再用
get_url()
获取最终的链接:
生成第一个请求:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def createUrls(self):
search_setting = self.read_excel()
urls = []
for setting in search_setting:
year_from = setting.split(",")[0]
year_to = setting.split(",")[1]
magazine_setting = setting.split(",")[2]
search_parameters = {'action': '', 'ua': '1.21',
'PageName': 'ASP.brief_result_aspx', 'DbPreFix': 'CJFQ',
'DbCatalog': '中国学术期刊网络出版总库', 'ConfigFile': 'CJFQ.xml',
'db_opt': 'CJFQ', 'db_value': '中国学术期刊网络出版总库',
'magazine_value1': magazine_setting, 'year_type': 'echar',
'year_from': year_from, 'year_to': year_to,
'magazine_special1': '='
}
origin_url = "http://kns.cnki.net/kns/request/SearchHandler.ashx"
url = self.get_url(origin_url, search_parameters)
urls.append(url)
return urls将 Excel 文件中读取的搜索条件通过
createUrls()
方法生成可迭代的请求列表。 生成第二个请求:1
2
3
4
5result_parameters = {'pagename': 'ASP.brief_result_aspx', 'DbPreFix': 'CJFQ',
'Research': 'off', 'KeyValue': '', 'S': '1', 'recordsperpage': '50'
, "sorttype": "(被引频次,'INTEGER') desc"}
origin_url = "http://kns.cnki.net/kns/brief/brief.aspx"
act_url = self.get_url(origin_url, result_parameters)第二个请求中增加了
recordsperpage
和sorttype
两个参数,前者代表每页显示的记录条数(Sprider 每采集 13 页左右,知网会提示输入验证码,所以设置为最大值 50 减少结果的总页数),后者代表结果的排序方式(采集引证文献时,由于一些文献是没有被引用的,所以采用按被引量递减的方式排序去过滤掉无用的请求,从而缩短采集时间)。
get_url()
方法:1
2
3
4
5
6def get_url(self, origin_url, parameters):
act_url = origin_url + "?"
for key in parameters.keys():
act_url = act_url + key + "=" + urllib.request.quote(str(parameters[key])) + "&"
act_url = act_url.strip("&")
return act_urlurllib.request.quote()
中文字符进行编码处理。发送
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def start_requests(self):
headers_parameters = {
'Host': 'kns.cnki.net',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36',
'Referer': 'http://kns.cnki.net/kns/brief/result.aspx?dbPrefix=CJFQ'
}
urls = self.createUrls()
for i, url in enumerate(urls):
yield scrapy.Request(url, meta={'cookiejar': i},
callback=self.result_page, headers=headers_parameters)
def result_page(self, response):
result_parameters = {'pagename': 'ASP.brief_result_aspx', 'DbPreFix': 'CJFQ',
'Research': 'off', 'KeyValue': '', 'S': '1', 'recordsperpage': '50'
, "sorttype": "(被引频次,'INTEGER') desc"}
origin_url = "http://kns.cnki.net/kns/brief/brief.aspx"
act_url = self.get_url(origin_url, result_parameters)
return scrapy.Request(act_url,
meta={'cookiejar': response.meta['cookiejar']},
callback=self.parse, dont_filter=True)meta={'cookiejar': i}
保存的是 cookie 用于保持持久性连接。There is support for keeping multiple cookie sessions per spider by using the cookiejar Request meta key. By default it uses a single cookie jar (session), but you can pass an identifier to use different ones. For example:
1
2
3for i, url in enumerate(urls):
yield scrapy.Request(url, meta={'cookiejar': i},
callback=self.parse_page)start_requests(self)
在获取到搜索结果之后回调result_page(self, response)
进行进一步处理。meta={'cookiejar': response.meta['cookiejar']}
取出 cookie 值向下传递。Keep in mind that the cookiejar meta key is not “sticky”. You need to keep passing it along on subsequent requests. For example:
1
2
3
4
5def parse_page(self, response):
# do some processing
return scrapy.Request("http://www.example.com/otherpage",
meta={'cookiejar': response.meta['cookiejar']},
callback=self.parse_other_page)dont_filter=True
由于第二次请求的链接相同,Scrapy 默认会过滤到这些请求,所以必须设置为 True。
配置访问异常重试
为了尽可能保证采集数据的完整性,需要配置 setting.py
文件加入如下代码: 1
2
3
4
5
6# 当访问异常时是否进行重试
RETRY_ENABLED = True
# 当遇到以下 http 状态码时进行重试
RETRY_HTTP_CODES = [500, 502, 503, 504, 400, 403, 404, 408]
# 重试次数
RETRY_TIMES = 5
参考资料
Scrapy 文档