中国知网上中国地震局相关数据采集的实现

一、项目需求

具体需求如下:

以“单位”为分类,“中国地震局”为关键词,跨选“期刊、教育期刊、特色期刊、博士、硕士、国内会议、国际会议、报纸、学术辑刊”几个数据库进行搜索,将搜索到的结果及详情页相关字段提取出来,存入一个CSV文件。

二、需求分析

了解了上面的具体需求,乍一看网站,似乎挺容易,实则不然!

且看采集步骤:

1、在搜索列表中提取文章详情页链接及其所在数据库存入任务文件;

2、多线程采集任务文件中的所有任务

简单看只有这两步,实际上难点就在第一步。

难点:

1、如何得到列表页

当按条件搜索得到列表后,想在列表里提取详情页链接,这时打开源文件发现没有文章列表。

不用怀疑,列表肯定是ajax加载的。

好吧,那就用Fiddler来找出ajax请求。

下面两图对比网站搜索列表页和Fiddler抓取的请求结果页,信息是一致的。

然后在Fiddler中模拟请求,发现Cookie是必须的。

经过多次调试,发现Cookie是有时效的,所以必须使用Cookie管理器动态获取Cookie提供给请求的headers。

2、如何得到列表分页

当要打开列表分页的时候,发现列表最多显示120个分页,而且列表上是没有显示分页路径的。所以就按年度分类得到细分列表再加载分页吧。

思路这么定了,那就是得找年度分类有什么特点和分页列表请求是什么了?

还是通过Fiddler来寻找是哪些请求吧,如下两图已经框出了整个过程索要执行的请求。

3、在加载分页过程中,出现验证码问题!

当连续加载了16个分页后,网站就出现了验证码,这说明列表分页页面Cookie有效加载分页数是16个。那解决这个问题的方法就是判断页面出现验证码的时候,要重新获取网站动态Cookie,然后再执行搜索,接着从上次出现验证码的页面开始加载。

好了,以上内容已经把列表页获取详情页链接的操作分析清楚了,那下来就编码实现吧。

三、编写代码

# coding: utf-8

# 抓取CNKI(http://cnki.net/)中单位为“中国地震局”的相关文献数据。

import re

import sys

from webscraping import common, download

from lxml import etree

from collections import deque

import time

import cookielib

import urllib2

from urlparse import urljoin

import csv

DELAY = 3

FIELDS = ['T1', 'A1', 'AD', 'AB', 'FU', 'K1', 'JF1', 'JF2', 'PY', 'SN', 'DT', 'A3', 'CN', 'CT', 'CP', 'DB', 'URL']

#FIELDS_DES = ['论文标题', '作者全名', '作者地址', '摘要', '基金', '关键词', '出版物中文名', '出版物英文名', \

#'出版年', 'ISSN号', '文献类型', '导师', '会议名称', '会议时间/出版日期', '会议地点', '数据库', 'URL']

NUM_THREADS = 10

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36'

def scraper():

found = {}

writer = common.UnicodeWriter('cnki_CEA.csv')

writer.writerow(FIELDS)

#writer.writerow(FIELDS_DES)

# 任务队列

tasks = deque()

#for year in range(1997, 2020):

for year in range(1998, 2020):

#for year in [2017]:

# 年份、起始页码

tasks.append((str(year), 1))

# 将搜索列表上的文章详情页链接写入文件,以追加的方式,url唯一性

url_writer = common.UnicodeWriter('art_urls.csv', mode='ab', unique=True, unique_by=[0])

url_writer.writerow(['art_url', 'DB'])

while tasks:

task_year, task_start_pagenum = tasks.popleft()

print 'Task year = {}, start_pagenum = {}'.format(task_year, task_start_pagenum)

# 通过Cookie管理器动态获取网站加载的cookie

cookie_jar = cookielib.MozillaCookieJar()

opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie_jar))

D = download.Download(delay=DELAY, num_retries=1, opener=opener, proxy_file='proxies.txt')

# 每次加载页面获取页面cookie

get_cookie_url = 'http://kns.cnki.net/kns/brief/default_result.aspx'

cookie_text = D.get(get_cookie_url, read_cache=False)

# 初始化任务,通过ajax请求加载各年份下文章列表页,提取文章链接加入任务队列

# 1,执行搜索(9个数据库)(每次必须执行搜索,不能读取缓存)

#search_url = 'http://kns.cnki.net/kns/request/SearchHandler.ashx?action=&NaviCode=*&ua=1.11&PageName=ASP.brief_default_result_aspx&DbPrefix=SCDB&DbCatalog=%e4%b8%ad%e5%9b%bd%e5%ad%a6%e6%9c%af%e6%96%87%e7%8c%ae%e7%bd%91%e7%bb%9c%e5%87%ba%e7%89%88%e6%80%bb%e5%ba%93&ConfigFile=SCDBINDEX.xml&db_opt=CJFQ%2CCDFD%2CCMFD%2CCPFD%2CIPFD%2CCCND%2CCCJD&txt_1_sel=AF%24%25&txt_1_value1=%E4%B8%AD%E5%9B%BD%E5%9C%B0%E9%9C%87%E5%B1%80&txt_1_special1=%25&his=0&parentdb=SCDB'

# 只搜索“期刊”数据库的请求url

search_url = 'http://kns.cnki.net/kns/request/SearchHandler.ashx?action=&NaviCode=*&ua=1.11&PageName=ASP.brief_default_result_aspx&DbPrefix=SCDB&DbCatalog=%e4%b8%ad%e5%9b%bd%e5%ad%a6%e6%9c%af%e6%96%87%e7%8c%ae%e7%bd%91%e7%bb%9c%e5%87%ba%e7%89%88%e6%80%bb%e5%ba%93&ConfigFile=SCDBINDEX.xml&db_opt=CJFQ%2CCJRF%2CCJFN&txt_1_sel=AF%24%25&txt_1_value1=%E4%B8%AD%E5%9B%BD%E5%9C%B0%E9%9C%87%E5%B1%80&txt_1_special1=%25&his=0&parentdb=SCDB&__=Fri%20Sep%2028%202018%2009%3A12%3A58%20GMT%2B0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)'

search_result = D.get(search_url, read_cache=False)

# 2,get方法加载年份列表,提取年份和年份id构造不同年份下列表首页请求url,并将列表第一页中的文章链接写入文件

# http://kns.cnki.net/kns/group/doGroupLeft.aspx?action=1&Param=ASP.brief_default_result_aspx%23SCDB/%u53D1%u8868%u5E74%u5EA6/%u5E74%2Ccount%28*%29/%u5E74/%28%u5E74%2C%27date%27%29%23%u5E74%24desc/1000000%24/-/40/40000/ButtonView&cid=0&clayer=0

#get_year_url = 'http://kns.cnki.net/kns/group/doGroupLeft.aspx?action=1&Param=ASP.brief_default_result_aspx%23SCDB/%u53D1%u8868%u5E74%u5EA6/%u5E74%2Ccount%28*%29/%u5E74/%28%u5E74%2C%27date%27%29%23%u5E74%24desc/1000000%24/-/40/40000/ButtonView&cid=0&clayer=0'

# 只搜索“期刊”数据库时获取年份列表的请求url

get_year_url = 'http://kns.cnki.net/kns/group/doGroupLeft.aspx?action=1&Param=ASP.brief_default_result_aspx%23SCDB/%u53D1%u8868%u5E74%u5EA6/%u5E74%2Ccount%28*%29/%u5E74/%28%u5E74%2C%27date%27%29%23%u5E74%24desc/1000000%24/-/40/40000/ButtonView&cid=0&clayer=0&__=Fri%20Sep%2028%202018%2009%3A19%3A47%20GMT%2B0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)'

year_list = D.get(get_year_url, read_cache=False)

if year_list:

for year_id, year in re.compile(r'

page_num = 1

if year == task_year:

query_id = ''

# 构造年份列表页 &recordsperpage=50 每页显示50条

year_url = 'http://kns.cnki.net/kns/brief/brief.aspx?ctl={}&dest=%E5%88%86%E7%BB%84%EF%BC%9A%E5%8F%91%E8%A1%A8%E5%B9%B4%E5%BA%A6%20%E6%98%AF%20{}&action=5&dbPrefix=SCDB&PageName=ASP.brief_default_result_aspx&Param=%e5%b9%b4+%3d+%27{}%27&SortType=%e5%b9%b4&ShowHistory=1&recordsperpage=50'.format(year_id, year, year)

year_first_html = D.get(year_url, read_cache=False)

# 提取总页数

page_num = common.regex_get(year_first_html, r'\d+/(\d+)')

# 提取QueryID

query_id = common.regex_get(year_first_html, r'&QueryID=(\d+)&')

#open(year + '.html', 'w').write(year_first_html)

# 第一页

if task_start_pagenum == 1 and year_first_html:

root = etree.HTML(year_first_html)

art_table = root.xpath('//table[@class="GridTableContent"]')

if art_table:

for tr in art_table[0].xpath('./tr'):

art_url = tr.xpath('string(./td[2]/a/@href)')

if art_url:

db_name = tr.xpath('string(./td[6])').strip()

if year == '2019':

art_url_id = common.regex_get(art_url, r'&URLID=([^=\'"]+)')

art_url = 'http://kns.cnki.net/KCMS/detail/' + art_url_id + '.html'

print 'Found art_url : {}'.format(art_url)

if art_url not in found:

found[art_url] = 1

# 将文章链接, 所属数据库 写入文件

url_writer.writerow([art_url, db_name])

else:

dbcode = common.regex_get(art_url, r'DbCode=([A-Z]+)&')

dbname = common.regex_get(art_url, r'DbName=([^&=]+)&')

filename = common.regex_get(art_url, r'FileName=([^&=]+)&')

art_url = 'http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=' + dbcode + '&dbname=' + dbname + '&filename=' + filename

print 'Found art_url : {}'.format(art_url)

if art_url not in found:

found[art_url] = 1

# 将文章链接, 所属数据库 写入文件

url_writer.writerow([art_url, db_name])

# 构造列表分页请求

# http://kns.cnki.net/kns/brief/brief.aspx?curpage={}&RecordsPerPage=50&QueryID=16&ID=&turnpage=1&tpagemode=L&dbPrefix=SCDB&Fields=&DisplayMode=listmode&SortType=%e5%b9%b4&PageName=ASP.brief_default_result_aspx&ctl=d6a6b300-5785-4953-b005-47dcfffdcb51&Param=%e5%b9%b4+%3d+%272018%27

if page_num:

page_num = int(page_num)

if page_num > 1:

for page in range(task_start_pagenum + 1, page_num + 1):

print 'page', page

get_page_url = 'http://kns.cnki.net/kns/brief/brief.aspx?curpage={}&RecordsPerPage=50&QueryID={}&ID=&turnpage=1&tpagemode=L&dbPrefix=SCDB&Fields=&DisplayMode=listmode&SortType=%e5%b9%b4&PageName=ASP.brief_default_result_aspx&ctl={}&Param=%e5%b9%b4+%3d+%27{}%27'.format(page, query_id, year_id, year)

paging_html = D.get(get_page_url, read_cache=False)

if 'class="GridTableContent"' in paging_html:

root = etree.HTML(paging_html)

art_table = root.xpath('//table[@class="GridTableContent"]')

valid_num = 0

if art_table:

for tr in art_table[0].xpath('./tr'):

art_url = tr.xpath('string(./td[2]/a/@href)')

if art_url:

valid_num += 1

db_name = tr.xpath('string(./td[6])').strip()

dbcode = common.regex_get(art_url, r'DbCode=([A-Z]+)&')

dbname = common.regex_get(art_url, r'DbName=([^&=]+)&')

filename = common.regex_get(art_url, r'FileName=([^&=]+)&')

art_url = 'http://kns.cnki.net/KCMS/detail/detail.aspx?dbcode=' + dbcode + '&dbname=' + dbname + '&filename=' + filename

print 'Found art_url : {}'.format(art_url)

if art_url not in found:

found[art_url] = 1

# 将文章链接, 所属数据库 写入文件

url_writer.writerow([art_url, db_name])

common.logger.info('Total found art_urls {}---{}'.format(valid_num, get_page_url))

else:

#open('invalid_list.html', 'w').write(paging_html)

common.logger.error('Invalid page--{}'.format(get_page_url))

print 'Add task_year = {}, task_start_pagenum = {} into task queue'.format(task_year, page - 1)

tasks.appendleft((task_year, page - 1))

break

# 以下详情页字段提起代码比较简单,在此就不详述了..............................

以上就是本项目主要思路和核心代码,有感兴趣的同学可自行尝试,不懂的地方欢迎留言询问。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180928G0O40L00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

同媒体快讯

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励