Skip to content

Commit

Permalink
中国知网爬虫
Browse files Browse the repository at this point in the history
  • Loading branch information
yanzhou committed Nov 5, 2014
0 parents commit a7e68a2
Show file tree
Hide file tree
Showing 29 changed files with 8,109 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .gitignore
@@ -0,0 +1,9 @@
.DS_Store
/.quarantine
/.tmb
*~
*pyc
/nbproject/*
.buildpath
.project
.settings
Empty file added data/.gitkeep
Empty file.
Empty file added data/ListPages/.gitkeep
Empty file.
Empty file added doc/.gitkeep
Empty file.
1 change: 1 addition & 0 deletions doc/categories.json

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions readme.md
@@ -0,0 +1,25 @@
1. 在src/CnkiSpider.py设置检索条件

2. 执行src/CnkiSpider.py抓取数据

3. 抓取数据存储在/data目录下,文件名格式为"data-keyword-年月日时分秒.txt.txt",如"data-新媒体-20131128224556.txt"

4. 每个数据文件的第一行为字段名称

5. 每次运行都根据当前时间生成新的数据文件

6. 如果抓取过程中断,可以在src/CnkiSpider.py中设置startPage为中断时的页码,并重新运行src/CnkiSpider.py从中断的页面继续抓取,最后将各个数据文件合并

7. 生成的文本文件直接修改后缀名为.csv然后用LibreOffice打开并在LibreOffice中设置字段分隔符为src/CnkiSpider.py中变量fieldsSep设置的字符串

8. Windows下打开Excel 2013,然后【打开】->【浏览】->选择文件(文件名后下拉框选择“文本文件”),出现文本导入向导,设置“文件原始格式”为Unicode(UTF-8),下一步,设置“分隔符号”

9. 由若要使用文本编辑器打开数据文件,建议使用Notepad++打开。Windows自带的记事本打开大文件会卡死。Notepad++可以自动识别编码格式,防止乱码。

10. 如果数据文件中从某部分开始大量出现关键词字段和分类号字段为空的情况,则将src/CnkiSpider.py中restEvery变量调小,restPeriod变量调大后重试。

## diff Windows version and Linux version

CnkiSpider.py print "----CONTENT:获取第" + str(article["order"]) + "篇文章"

ContentSpider.py s = s.replace("【分类号】".decode("utf8"), "")
106 changes: 106 additions & 0 deletions src/CnkiSpider.py
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
"""
抓取CNKI的主程序
默认检索所有学科的"文献"类型的文章(包含期刊,硕士论文,博士论文等)
注意:1.在使用数据前请浏览以确认所有字段格式无误,如某些文献的作者字段可能会多出换行符未处理掉,将导致转换成xls文件时多出空白行
2.不含关键字字段和分类字段的文献很可能是内容页没有成功抓取的,这个bug以后改进
3.摘要的内容代码里面已成功抽取,若要添加需要修改ContentSpider.py(69-71),Config.py(29),CnkiSpider.py(70)
4.中断后从断点页面继续爬取请设置startPage参数
5.数据文件名名称格式为:"data-keyword-年月日时分秒.txt",如"data-新媒体-20131128141415.txt"中断后继续爬取会重新根据当前时间生成新的文件,最后将多个文件合并即可
TODO:1.网络超时控制(暂时解决办法:抓取中断后设置startPage为中断页重新抓取)
2.根据内容页url抓取内容页会被跳转到CNKI首页从而导致内容页无法抓取,即关键字和分类号字段缺失
3.设置配置文本文件,配置项写如其中而不必修改该文件来设置配置项
4.智能评估抓取多少页后需要休息多长时间才能避免抓取过快在内容页时被从定向到CNKI首页而无法获取内容详情页信息
"""
import codecs
import time
from Config import Config
from Cookie import Cookie
from ListSpider import ListSpider
from ContentSpider import ContentSpider

"""
设置部分
更多自定义设置请参照Config.py文件定义
"""
config = Config()
#设置搜索关键词
keyword = "新媒体"
config.set("txt_1_value1", keyword, "search")
#设置关键字检索范围:"FT$%=|"(全文);"SU$%=|" selected="true"(主题);"TI$%=|"(篇名);"KY$=|"(关键词);"AU$=|"(作者);"AF$%"(单位);"LY"(刊名);"SN$=|??"(ISSN);"CN$=|??"(CN);"FU"(基金);"AB$%=|"(摘要);"RF$%=|"(参考文献);"CLC$=|??"(中图分类号);
x = "FT$%=|"
config.set("txt_1_sel", x, "search")
#设置检索匹配方式,"%"(模糊查询),"="(精确查询)
match = "%"
config.set("txt_1_special1", match, "search")
#设置检索学科,默认检索所有学科(*)。各学科代码参见 ./doc/categories.json文件
discipline = "*"
config.set("NaviCode", discipline, "search")
#设置字段分隔符
fieldsSep = "###"
config.set("fieldsSeperator", fieldsSep)
#设置行分隔符,若数据文件主要在Windows下使用,则设置为\r\n,若Linux下则设置为\r或\r\n
lineSep = "\r\n"
config.set("lineSeperator", lineSep)
#每抓取restEvery页列表页后休息restPeriod秒
restEvery = 20
config.set("restEvery", restEvery)
restPeriod = 60
config.set("restPeriod", restPeriod)

#起始列表页,用于中断后接着爬取
startPage = 1

cookie = Cookie(config)
listspider = ListSpider(config, cookie)
contentspider = ContentSpider(config, cookie)

#打开存储内容的文件
fileName = config.get("outputPath") + "data-" + config.get("txt_1_value1", "search").decode("utf8") + "-" + time.strftime("%Y%m%d%H%M%S") + ".txt"
handler = codecs.open(fileName,'a', encoding='utf-8')
#字段分隔符
sep = config.get("fieldsSeperator")
#数据表头
header = ""
fields = config.get("fieldsOrder")
for field in fields:
header += field + sep
header = header[0:len(header) - len(sep)] + config.get("lineSeperator")
handler.write(header)

# 获取列表页总页数
totalListPages = cookie.getTotalListPage()
print "################START(" + time.strftime("%Y-%m-%d %H:%M:%S") + ")列表页总数:" +str(totalListPages) + "################"
for i in range(startPage, totalListPages + 1):
print "LIST:获取列表页:" + str(i) + "/" + str(totalListPages)
#每抓取config["restEvery"]页列表页后休息config["restPeriod"]秒
if i % config.get("restEvery") == 0:
print "REST(" + time.strftime("%Y-%m-%d %H:%M:%S") + "):又抓取了" + str(config.get("restEvery")) + "页了,休息" + str(config.get("restPeriod")) + "秒吧~"
time.sleep(config.get("restPeriod"))

articlesList = listspider.getArticles(listspider.fetchHtml(i))
for article in articlesList:
print "----CONTENT:获取第" + str(article["order"]) + "篇文章"
contentHtml = contentspider.fetchHtml(article["url"], int(article["order"]))
content = contentspider.getDetailInfo(contentHtml)
#从article和content抽取信息存储
save = {}
save["order"] = article["order"]
save["authors"] = article["authors"]
save["source"] = article["source"]
save["time"] = article["time"]
save["db"] = article["db"]
save["cited"] = article["cited"]
save["downloaded"] = article["downloaded"]
save["title"] = article["title"]
save["keywords"] = content["keywords"]
save["categories"] = content["categories"]
#save["abstract"] = content["abstract"]
#存储之前将所有字符过滤一遍,去掉换行符,去掉两端的空白
for k in save:
save[k] = save[k].replace("\r\n", "")
save[k] = save[k].strip()
contentspider.save(save, handler)
handler.close()
print "################END(" + time.strftime("%Y-%m-%d %H:%M:%S") + ")################"

153 changes: 153 additions & 0 deletions src/Config.py
@@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
import time

class Config:
"""
配置类
"""

def __init__(self):
"""
构造函数
设置默认配置项
"""
self.config = {}
"""
系统配置
"""
# 保存抓取数据的文件路径,默认为当前文件夹下data.txt文件
self.config["outputPath"] = "../data/"
#列表页保存路径,需要手动创建好文件夹
self.config["outputListPagesDir"] = "../data/ListPages/"
# 列表页Base URL,用于加上其他参数构造列表页URL
self.config["listBaseUrl"] = "http://epub.cnki.net/kns/brief/brief.aspx"
# 内容页Base URL,用于加上其他参数构造内容页URL
self.config["contentBaseUrl"] = "http://epub.cnki.net/kns/detail/detail.aspx"
# 输出各个字段之间的分割符
self.config["fieldsSeperator"] = "###"
#输出的各个字段及顺序
self.config["fieldsOrder"] = ["order","title","time","keywords","categories","downloaded","cited","source","authors","db"]
#换行符号
self.config["lineSeperator"] = "\r\n"
#需要输入验证码时刷新Cookie的次数
self.config["refreshCookieTimes"] = 3
#刷新Cookie时间间隔
self.config["refreshCookieInterval"] = 5
#每抓取config["restEvery"]个页面列表页后休息config["restPeriod"]秒
self.config["restEvery"] = 20
self.config["restPeriod"] = 60
#urlopen发生异常后重试时间间隔
self.config["urlopenExceptRetryInterval"] = 120
"""
请求头配置
"""
self.config["headers"] = {}
self.config["headers"]["User-Agent"] = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0"

"""
搜索表单配置
配置项可选参数:
ConfigFile: "SCDBINDEX.xml"
dbCatalog: "中国学术文献网络出版总库"
dbPrefix(文献类型): "SCDB"(文献);"CJFQ"(期刊);"CDMD"(博硕士);"CIPD"(会议);"CCND"(报纸);
"WWJD"(外文文献);"CYFD"(年鉴);"CRPD"(百科);"CRDD"(词典);
"CSYD"(统计数据);"SCOD"(专利);"CISD"(标准);"IMAGE"(图片);"SNAD"(成果);
"CIDX"(指数);"CLKD"(法律);"GXDB_SECTION"(古籍);"CRLD"(引文);"CRMD"(手册)
NaviCode(文献分类编码):"*"(all,default)参见categories.json,如"A001"表示"自然科学理论与方法"
PageName:保持默认值"ASP.brief_default_result_aspx",不许要修改
__(当前时间字符串):形式为"Tue Nov 26 2013 23:02:44 GMT+0800 (CST)"
action:留空
db_opt(可选数据库,一般跟DbPrefix相同,若DbPrefix为SCDB则该值可以是多个dbPrefix可选值用逗号分隔的字符串):如"CJFQ,CJFN,CDFD"
his():"0"
parentdb():"SCDB"
txt_1_sel(关键字搜索范围):"FT$%=|"(全文);"SU$%=|" selected="true"(主题);"TI$%=|"(篇名);
"KY$=|"(关键词);"AU$=|"(作者);"AF$%"(单位);"LY"(刊名);"SN$=|??"(ISSN);
"CN$=|??"(CN);"FU"(基金);"AB$%=|"(摘要);"RF$%=|"(参考文献);"CLC$=|??"(中图分类号);
txt_1_special1(模糊或者精确查询):"%"(模糊查询),"="(精确查询)
txt_1_value1(搜索关键词):如"自媒体"
ua(子查询检索):保持默认值"1.11"不要修改,"1.11"(直接检索),"1.12"(在有侧栏中检索),"1.16"(在结果中检索)
DisplayMode(列表页显示模式):"listmode"(列表模式),"custommode"(摘要模式)
S: 1
dbPrefix(文献类型): "SCDB"(文献);"CJFQ"(期刊);"CDMD"(博硕士);"CIPD"(会议);"CCND"(报纸);
"WWJD"(外文文献);"CYFD"(年鉴);"CRPD"(百科);"CRDD"(词典);
"CSYD"(统计数据);"SCOD"(专利);"CISD"(标准);"IMAGE"(图片);"SNAD"(成果);
"CIDX"(指数);"CLKD"(法律);"GXDB_SECTION"(古籍);"CRLD"(引文);"CRMD"(手册)
keyValue(要搜索的关键词):如"自媒体"
pagename:"ASP.brief_default_result_aspx"
research:"off"
t(系统时间毫秒数):int(time.time() * 1000)
"""
self.config["search"] = {}
self.config["search"]["ConfigFile"] = "SCDBINDEX.xml"
self.config["search"]["dbCatalog"] = "中国学术文献网络出版总库"
self.config["search"]["dbPrefix"] = "SCDB"
self.config["search"]["NaviCode"] = "*"
self.config["search"]["PageName"] = "ASP.brief_default_result_aspx"
self.config["search"]["__"] = time.strftime("%a %b %d %Y %H:%M:%S GMT+0800 (CST)")
self.config["search"]["action"] = ""
self.config["search"]["db_opt"] = "CJFQ,CJFN,CDFD,CMFD,CPFD,IPFD,CCND,CCJD,HBRD"
self.config["search"]["his"] = "0"
self.config["search"]["parentdb"] = "SCDB"
self.config["search"]["txt_1_sel"] = "FT$%=|"
self.config["search"]["txt_1_special1"] = "%"
self.config["search"]["txt_1_value1"] = "新媒体"
self.config["search"]["ua"] = "1.11"

"""
重要
生成cookie的时候需要爬取列表页第一页,发送检索选项
爬取第一个列表页的时候参数不同
"""
self.config["listPageOne"] = {}
self.config["listPageOne"]["ConfigFile"] = self.config["search"]["ConfigFile"]
self.config["listPageOne"]["S"] = "1"
self.config["listPageOne"]["dbCatalog"] = self.config["search"]["dbCatalog"]
self.config["listPageOne"]["dbPrefix"] = self.config["search"]["dbPrefix"]
self.config["listPageOne"]["keyValue"] = self.config["search"]["txt_1_value1"]
self.config["listPageOne"]["pagename"] = self.config["search"]["PageName"]
self.config["listPageOne"]["research"] = "off"
self.config["listPageOne"]["t"] = int(time.time() * 1000)

"""
爬取列表页和内容页所需要的参数
保持默认即可
"""
self.config["list"] = {}
self.config["list"]["DisplayMode"] = "listmode"
self.config["list"]["Fields"] = ""
self.config["list"]["ID"] = ""
self.config["list"]["PageName"] = "ASP.brief_default_result_aspx"
self.config["list"]["QueryID"] = "0"
self.config["list"]["RecordsPerPage"] = "20"
self.config["list"]["dbPrefix"] = "SCDB"
self.config["list"]["sKuaKuID"] = "0"
self.config["list"]["tpagemode"] = "L"
self.config["list"]["turnpage"] = "1"
self.config["list"]["curpage"] = "1"

def get(self, key, parent=None):
"""
获取配置
key:配置项键名
parent:上级键名,search,list或None
"""
if key and key in self.config.keys():
return self.config[key]
elif parent in ["list", "search", "headers", "listPageOne"]:
return self.config[parent][key]
else:
return None

def set(self, key, val, parent=None):
"""
设置配置项
key:键名
val:键值
parent:上级键名,search,list或None
"""
if key and val and key in self.config.keys():
self.config[key] = val
elif key and val and parent in ["list", "search", "headers", "listPageOne"]:
self.config[parent][key] = val

0 comments on commit a7e68a2

Please sign in to comment.