Stata连享会 主页 || 视频 || 推文 || 知乎 || Bilibili 站
温馨提示: 定期 清理浏览器缓存,可以获得最佳浏览体验。
New!
lianxh
命令发布了:
随时搜索推文、Stata 资源。安装:
. ssc install lianxh
详情参见帮助文件 (有惊喜):
. help lianxh
连享会新命令:cnssc
,ihelp
,rdbalance
,gitee
,installpkg
⛳ Stata 系列推文:
作者:王文韬 (山东大学)
邮箱:190138445@qq.com
Python爬虫: 《经济研究》研究热点和主题分析
目录
⭕ 本文爬虫数据及 py 文件下载地址:
https://file.lianxh.cn/data/c/cnki_erj_data.zip
爬虫,顾名思义,指我们沿着网络抓取自己的猎物(数据)。使用这种方法,我们可以向网站发起请求,获取资源后分析并提取有用数据。 相比与其他静态编程语言,如 java,c#,C++,python 抓取网页文档的接口更简洁;相比其他动态脚本语言,如 perl,shell,python 的 urllib2 包提供了较为完整的访问网页文档的 API。
此外, 抓取网页有时候需要模拟浏览器的行为,譬如模拟用户登陆、模拟 session/cookie 的存储和设置。在 python 里有非常优秀的第三方包帮你搞定 ,如requests
。
抓取的网页通常需要处理,比如过滤 html 标签,提取文本等。python 的 beautifulsoap
提供了简洁的文档处理功能 ,能用极短的代码完成大部分文档的处理。
1)本文分析网页时使用 fildder
进行抓包,配合谷歌浏览器 F12 检查。使用文本 IDE、vscode 编写爬虫代码,requests
库获取网页信息,beautifulsoup
库和正则表达式 re
解析网页,xlwt
写入为 xls 格式。
2)数据分析与可视化工具为 Jupyter Notebook,用 Pandas
库进行数据处理,matplotlib
画图等。
本文目标为模拟知网的高级搜索,获取特定学术期刊的历史论文信息。 以国内经济学顶刊《经济研究》为例,获取其 2000-2018 年的全部文章数据。具体包括摘要页的题名、作者、来源、发表时间、数据库、被引、下载,以及详细页面的单位、关键字、摘要等信息,以探究近二十年来国内核心期刊发文概况及研究热点的变动趋势。
本文主要由几下三部分构成:
本文需要的高级检索页面如下:
爬虫实现上来看,需要在维持 session 的情况下,多次向服务器发送信息,来获得目标页面。主要有三个要实现的动作: 网页(知网)高级检索、打开详细界面与翻页。
上述三个动作的实现,关键在于信息的有效提交和获取。此处有两大难点(这是 Python 爬虫的基本功,可参考北理工嵩天老师MOOC课程
学习巩固):
(1) 抓包后,哪些是需要关注的请求? 答: 本文将主要请求提取出来,知网中的每一个请求都是按照请求方法的作用进行命名,这在很大程度上能够帮助我们快速定位到我们需要的请求。 多次尝试后,我们大致定位出每个请求的信息,详情见下页图。
(2) 找到需要关注的请求后,如何构建请求? 答: 具体来说,每次 get 请求中,我们上传的参数如何设置。根据 fildder 我们可以获取需要上传的参数。这些参数中有很多都是固定的,也有一部分是动态的。动态的一般就是检索条件需要的,需要注意是如果没有规定某个条件,那这个参数是不传递的。下面列出了一部分常用的传递参数,更多参数大家自己通过这个方法查看。至此我们了解的如何获取页面请求以及如何设置搜索参数,下面我们可以开始进行爬虫代码编写。
浏览器正常浏览下,三个动作的抓包如下所示:
爬虫 requests 模拟下,三个动作的抓包:
爬取网页数据需要设置的参数:
上图红框中参数信息详情:
第一,需要设置我们的报文信息,模拟真人访问,用于回避网站的审查
def crawl_headers(self):
headers = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',
'Host':
'kns.cnki.net',
'Connection':
'keep-alive',
'Cache-Control':
'max-age=0',
}
return headers
第二,发送请求,并捕捉到我们所需的内容(核心关键步骤)
def search_reference(self, ueser_input):
'''
第一次发送post请求
再一次发送get请求,这次请求没有写文献等东西
两次请求来获得文献列表
'''
#删除了些参数,并改变action赋值,便可完成时间区间的限定
static_post_data = {
'action': '44',
'NaviCode': '*',
'ua': '1.21',
'isinEn': '1',
'PageName': 'ASP.brief_result_aspx',
'DbPrefix': 'SCDB',
'DbCatalog': '中国学术文献网络出版总库',
'ConfigFile': 'SCDB.xml',
'db_opt': 'CJFQ,CDFD,CMFD,CPFD,IPFD,CCND,CCJD', # 搜索类别(CNKI右侧的)
'his': '0',
'__': time.asctime(time.localtime()) + ' GMT+0800 (中国标准时间)'
}
# 将固定字段与自定义字段组合
post_data = {**static_post_data, **ueser_input}
#print(post_data)
# 必须有第一次请求,否则会提示服务器没有用户
first_post_res = self.session.post(
SEARCH_HANDLE_URL, data=post_data, headers=HEADER)
# get请求中需要传入第一个检索条件的值
#key_value = quote(ueser_input.get('magazine_value1'))
#self.get_result_url = GET_PAGE_URL + first_post_res.text + '&t=1544249384932&keyValue=' + key_value + '&S=1&sorttype='
self.get_result_url = GET_PAGE_URL + first_post_res.text + '&t=1562315821144&keyValue=&S=1&sorttype='
#print(self.get_result_url)
# 检索结果的第一个页面
second_get_res = self.session.get(self.get_result_url, headers=HEADER)
change_page_pattern_compile = re.compile(
r'.*?pagerTitleCell.*?<a href="(.*?)".*')
self.change_page_url = re.search(change_page_pattern_compile,
second_get_res.text).group(1)
self.parse_page(
self.pre_parse_page(second_get_res.text), second_get_res.text)
其中,要注意对函数 parse_page
和 get_detail_page
的构建:
1)parse_page
方法用来捕捉每一页我们要捕捉的内容,我们根据 class 和 name 对页面中每个 table 的内容进行捕捉,同时在这里使用beautifulsoap
方法对捕捉网页内容进行文本处理。
parse_page 方法代码如下:
def parse_page(self, download_page_left, page_source):
'''
保存页面信息
解析每一页的下载地址
'''
soup = BeautifulSoup(page_source, 'lxml')
# 定位到内容表区域
tr_table = soup.find(name='table', attrs={'class': 'GridTableContent'})
# 处理验证码
try:
# 去除第一个tr标签(表头)
tr_table.tr.extract()
except Exception as e:
logging.error('出现验证码')
return self.parse_page(
download_page_left,
crack.get_image(self.get_result_url, self.session,
page_source))
# 遍历每一行
for index, tr_info in enumerate(tr_table.find_all(name='tr')):
tr_text = ''
download_url = ''
detail_url = ''
# 遍历每一列
for index, td_info in enumerate(tr_info.find_all(name='td')):
# 因为一列中的信息非常杂乱,此处进行二次拼接
td_text = ''
for string in td_info.stripped_strings:
td_text += string
tr_text += td_text + '@'
#with open(
# 'data/ReferenceList.txt', 'a',
# encoding='utf-8') as file:
# file.write(td_text + ' ')
# 寻找下载链接
dl_url = td_info.find('a', attrs={'class': 'briefDl_D'})
# 寻找详情链接
dt_url = td_info.find('a', attrs={'class': 'fz14'})
# 排除不是所需要的列
if dt_url:
detail_url = dt_url.attrs['href']
if dl_url:
download_url = dl_url.attrs['href']
# 将每一篇文献的信息分组
single_refence_list = tr_text.split('@')
#print(single_refence_list)
#self.download_refence(download_url, single_refence_list)
self.download_url = DOWNLOAD_URL + re.sub(r'../', '', download_url)
# 是否开启详情页数据抓取
if config.crawl_isdetail == '1':
#time.sleep(config.crawl_stepWaitTime)
page_detail.get_detail_page(self.session, self.get_result_url,
detail_url, single_refence_list,
self.download_url, self.filename)
# 在每一行结束后输入一个空行
#with open('data/ReferenceList.txt', 'a', encoding='utf-8') as file:
# file.write('\n')
# download_page_left为剩余等待遍历页面
if download_page_left > 1:
self.cur_page_num += 1
self.get_another_page(download_page_left)
2)get_detail_page
为获取详情内容,即 get_detail_page 中我们通过 name 和 class 获取页面中的信息,之后利用 xlwt 包将内容保存到本地。
get_detail_page
局部代码如下:
def __init__(self):
# count用于计数excel行
self.excel = xlwt.Workbook(encoding='utf8')
self.sheet = self.excel.add_sheet('文献列表', True)
self.set_style()
self.sheet.write(0,0,'序号',self.basic_style)
self.sheet.write(0, 1, '题名',self.basic_style)
self.sheet.write(0, 2, '作者',self.basic_style)
self.sheet.write(0, 3, '单位',self.basic_style)
self.sheet.write(0, 4, '关键字',self.basic_style)
self.sheet.write(0, 5, '摘要',self.basic_style)
#新增-基金和总页数
self.sheet.write(0, 6, '基金',self.basic_style)
self.sheet.write(0, 7, '总页数',self.basic_style)
self.sheet.write(0, 8, '来源',self.basic_style)
self.sheet.write(0, 9, '发表时间',self.basic_style)
self.sheet.write(0, 10, '数据库',self.basic_style)
#新增-被引和下载
self.sheet.write(0, 11, '被引',self.basic_style)
self.sheet.write(0, 12, '下载',self.basic_style)
#if config.crawl_isDownLoadLink=='1':
# self.sheet.write(0, 9, '下载地址',self.basic_style)
# 生成userKey,服务器不做验证
self.cnkiUserKey=self.set_new_guid()
def get_detail_page(self, session, result_url, page_url,
single_refence_list, download_url, filename):
'''
发送三次请求
前两次服务器注册 最后一次正式跳转
'''
# 这个header必须设置
HEADER['Referer'] = result_url
self.single_refence_list=single_refence_list
self.session = session
self.session.cookies.set('cnkiUserKey', self.cnkiUserKey)
self.download_url=download_url
cur_url_pattern_compile = re.compile(
r'.*?FileName=(.*?)&.*?DbCode=(.*?)&')
cur_url_set=re.search(cur_url_pattern_compile,page_url)
# 前两次请求需要的验证参数
params = {
'curUrl':'detail.aspx?dbCode=' + cur_url_set.group(2) + '&fileName='+cur_url_set.group(1),
'referUrl': result_url+'#J_ORDER&',
'cnkiUserKey': self.session.cookies['cnkiUserKey'],
'action': 'file',
'userName': '',
'td': '1544605318654'
}
# 首先向服务器发送两次预请求
self.session.get(
'http://i.shufang.cnki.net/KRS/KRSWriteHandler.ashx',
headers=HEADER,
params=params)
#self.session.get(
# 'http://kns.cnki.net/KRS/KRSWriteHandler.ashx',
# headers=HEADER,
# params=params)
page_url = 'http://kns.cnki.net' + page_url
get_res=self.session.get(page_url,headers=HEADER)
#gbk_code = get_res.text.encode('GBK', 'ignore').decode('GBK')
#with open('test.txt','w') as f:
# f.write(gbk_code)
self.pars_page(get_res.text)
filename_xls = 'data/{}.xls'.format(filename)
#self.excel.save('data/CNKI.xls')
self.excel.save(filename_xls)
def pars_page(self,detail_page):
'''
解析页面信息
'''
soup=BeautifulSoup(detail_page,'lxml')
# 获取作者单位信息
orgn_list=soup.find(name='div', class_='orgn').find_all('a')
self.orgn=''
if len(orgn_list)==0:
self.orgn=''
#self.orgn='无单位来源'
else:
for o in orgn_list:
self.orgn+=o.string+';'
# 获取摘要
self.abstract=''
try:
abstract_list = soup.find(name='span', id='ChDivSummary').strings
for a in abstract_list:
self.abstract+=a
except:
pass
# 获取关键词
self.keywords=''
try:
keywords_list = soup.find(name='label', id='catalog_KEYWORD').next_siblings
for k_l in keywords_list:
# 去除关键词中的空格,换行
for k in k_l.stripped_strings:
self.keywords+=k
except:
#self.keywords='无关键词'
pass
#新增-获取基金
self.fund = ''
try:
fund_list = soup.find(name = 'label', id = 'catalog_FUND').next_siblings
for f_l in fund_list:
#print('f_l',f_l)
self.fund += f_l.string.strip().replace(';', ';')
except:
pass
#print('fund', self.fund)
#新增-获取总页码
self.total_page = ''
self.total_page = str(soup.find('div','total')('span')[2].b.string)
#print('total_page',self.total_page)
self.wtire_excel()
def create_list(self):
'''
整理excel每一行的数据
#(以前的为)序号 题名 作者 单位 关键字 摘要 来源 发表时间 数据库
序号 题名 作者 单位 关键字 摘要 基金 总页码 来源 发表时间 数据库 被引 下载
'''
#single_reference_list为搜索界面的数据
self.reference_list = []
for i in range(0,3):
self.reference_list.append(self.single_refence_list[i])
self.reference_list.append(self.orgn)
self.reference_list.append(self.keywords)
self.reference_list.append(self.abstract)
self.reference_list.append(self.fund)
self.reference_list.append(self.total_page)
for i in range(3,8):
self.reference_list.append(self.single_refence_list[i])
第三,当捕捉完当前页面内容后,我们需要捕捉下一个页面内容,即再一次对网站发送请求,代码如下:
def get_another_page(self, download_page_left):
'''
请求其他页面和请求第一个页面形式不同
重新构造请求
'''
#time.sleep(config.crawl_stepWaitTime)
curpage_pattern_compile = re.compile(r'.*?curpage=(\d+).*?')
self.get_result_url = CHANGE_PAGE_URL + re.sub(
curpage_pattern_compile, '?curpage=' + str(self.cur_page_num),
self.change_page_url)
get_res = self.session.get(self.get_result_url, headers=HEADER)
download_page_left -= 1
self.parse_page(download_page_left, get_res.text)
第四,根据自定义的时间范围,我们可以获取在这个区间内的所有论文信息,代码如下:
def main(user_input):
time.perf_counter()
search = SearchTools()
search.search_reference(user_input)
print('--------------------------')
print('共运行:'+s2h(time.perf_counter()))
time_range = list(range(2000,2018))
for index,i in enumerate(time_range):
user_input = {'magazine_value1':'经济研究'}
user_input['publishdate_from'] = '{}-01-01'.format(str(i))
user_input['publishdate_to'] = '{}-12-31'.format(str(i))
filename = filename = '{}({}~{})'.format(user_input['magazine_value1'], user_input['publishdate_from'], user_input['publishdate_to'])
print('现爬取年份为',i,'爬取区间为','{}~{}'.format(time_range[0],time_range[-1]-1))
print(filename)
if index != 0:
print('停止3秒')
time.sleep(3)
main(user_input)
print('爬取结束')
爬取结果如下:
爬取数据去重后,得到 3430 条数据。根据爬取到的信息,我们需要对数据进行预处理,以有无关键词作为衡量是否为学术文章的标准。
df_keyword = df[df['关键字'].notnull()]
df_keyword.loc[:,'年份'] = df_keyword.loc[:,'发表时间'].str[:4]
df_keyword = df_keyword.set_index(['年份'])
df_keyword.head()
之后我们对数据进行简单的统计分析。
使用正则表达式来计算摘要汉字个数,分析摘要字数变化趋势,趋势图与代码如下:
df1_keyword = df_keyword.copy()
df1_keyword.loc[:,'count'] = 1
df1_keyword.loc[:,'题名字数'] = df1_keyword.loc[:,'题名'].str.count(r'[\u4E00-\u9FA5]')
df1_keyword.loc[:,'作者人数'] = df1_keyword.loc[:,'作者'].str.count(';') + df1_keyword.loc[:,'作者'].str.count(',') + 1
df1_keyword.loc[:,'摘要字数'] = df1_keyword.loc[:,'摘要'].str.count(r'[\u4E00-\u9FA5]')
df1_keyword.loc[:,'摘要句数'] = df1_keyword.loc[:,'摘要'].str.count('。')
df1_keyword.loc[:,'基金数'] = df1_keyword.loc[:,'基金'].str.count(';')
df1_keyword['是否有基金'] = df1_keyword.loc[:,'基金'].notnull()
#df1_keyword['是否有基金'] = df1_keyword[df1_keyword['基金'].notnull()]
df1_keyword.head()
f, axes = plt.subplots(1, 1, sharey=True, figsize=(12, 8))
#sns.boxplot(x="day", y="tip", data=tips, ax=axes[0])
sns.swarmplot(x = df1_keyword.index, y = df1_keyword.loc[:,'摘要字数'], orient='v', size = 5, ax=axes)
摘要字数趋势图:
从结果来看,2000 年以来,在《经济研究》文献中作者写作的摘要字数逐年攀升,学者对问题的解析和思考日渐细致。
为研究被引和下载量的关系,我们利用 seaborn 画出如下图:
x = df1_keyword.loc[:,'下载']
y = df1_keyword.loc[:,'被引']
sns.jointplot(x, y, color="#4CB391",xlim = (0,20000),ylim = (0,1000),kind="reg" )
下载与被引线性拟合图:
这里对下载与被引数使用 python 语言进行一次简单的线性回归(结果并非十分严谨准确),可以在一定程度上认为二者呈现正相关关系。
进一步我们分析相关领域的主要作者,对全部文献进行统计:
all_author_raw = list(df1_keyword.loc[:,'作者'])
all_author = []
for i in all_author_raw:
try:
one_paper_author_list = i.replace(',',';').split(';')
for k in one_paper_author_list:
all_author.append(k)
except:
all_author.append(i)
#len(all_author)
counts = {}
for author in all_author:
counts[author] = counts.get(author,0) + 1
counts.pop('')
items = list(counts.items())
items.sort(key = lambda x:x[1], reverse = True)
print('出现次数排名前十的作者')
items[:10]
我们得出出现次数前 10 位的作者如下:
对发文的大学与科研机构进行统计分析。分析代码与结果如下:
all_organ_raw = list(df1_keyword.loc[:,'单位'])
all_organ_raw1 = []
for i in all_organ_raw:
try:
one_paper_organ_list = i.replace(',',';').split(';')
for k in one_paper_organ_list:
all_organ_raw1.append(k)
except:
all_organ_raw1.append(i)
#all_organ_raw1
all_organ = []
for i in all_organ_raw1:
try:
all_organ.append(re.match(r'[\u4E00-\u9FA5]+',i).group(0))
except:
pass
for index,organ in enumerate(all_organ):
if organ == '邮政编码':
all_organ.pop(index)
len(all_organ)
all_univ = []
for i in all_organ:
try:
s = i.index('大学')+2
all_univ.append(i[:s])
except:
pass
#all_univ
print('大学总出现次数(重复计算)',len(all_univ))
count = {}
for i in all_univ:
count[i] = count.get(i,0)+1
items = list(count.items())
items.sort(key = lambda x:x[1], reverse = True)
x = 20
rank = pd.DataFrame(items[:x], index = list(range(1,x+1)))
rank.index.name = '排名'
rank.columns = ['大学','次数']
rank
count = {}
for i in all_organ:
count[i] = count.get(i,0)+1
items = list(count.items())
items.sort(key = lambda x:x[1], reverse = True)
#想要前x位数据
x = 20
want_list = items[:x]
name = [want_list[i][0] for i in range(x)]
num = [want_list[i][1] for i in range(x)]
organ_rank = pd.DataFrame(want_list,index = list(range(1,x+1)))
organ_rank.index.name = '排名'
organ_rank.columns = ['机构','次数']
organ_rank
发文学校排名结果:
可以看到北京大学以 402 发文数量遥遥领先,中国人民大学以 271 篇位居第二,上海财经大学、复旦大学、厦门大学与中山大学旗鼓相当处于 3-6 位。
发文机构排名结果:
具体到发文机构(学院),中国社会科学院经济研究所以 140 篇位居第一,北大光华学院紧跟其后。
对比上述两组结果,我们观察到华中科技大学经济学院表现亮眼,其在《经济研究》中 40 篇发文数量与上海财经大学经济学院仅差 1 篇,超过中山管院、厦大管院、山大经院等知名院校。
此外,我们对文章的关键词进行按年统计分析,提取"政策","税收","制度","增长","企业"五个关键词观察其每年使用次数的走势,代码如下:
zhaiyao_df = pd.concat(zhaiyao_lcut)
zhaiyao_df.columns = ['关键词','词频','年份']
zhaiyao_df =zhaiyao_df.set_index(['年份','关键词'])
zhaiyao_df.head()
zhaiyao_df.sum(level = '年份').T
cipin = zhaiyao_df.sum(level = '关键词')
#注意此处与列表sort的区别,列表中关键词为key,倒转为reverse = True
cipin = cipin.sort_values(by = '词频', ascending = False)
#查看前10词频排名
cipin.head(10).T
#查看单个词汇是否出现,以及总的出现频次
cipin.loc['价值链']
zhaiyao_df_w = zhaiyao_df.unstack('关键词')
zhaiyao_df_w.info()
zhaiyao_df_w1 = zhaiyao_df_w.copy()
zhaiyao_df_w1.columns = zhaiyao_df_w.columns.swaplevel(0,1)
want_keyword = ['政策','税收','制度','增长','企业']
#注意index不能用:,而要用slice(None)替代
data = zhaiyao_df_w1.loc[:,(want_keyword,slice(None))].fillna(0)
data
ax = sns.lineplot(data = data)
a = ax.set_xticklabels(data.index,rotation=60)
提取的五个关键词走势图如下:
可以看出,近年来对企业、税收以及政策问题的研究文献越来越多,统计结果意味着:一方面微观企业部门依然是学界密切关注的话题,另一方面对我国经济政策,包括对税收政策的讨论,持续升温。
本文主要介绍了利用 python 爬虫对知网文献信息进行爬取,并利用相关技术对爬取的信息进行简单的分析。而更多的 Python 爬虫方法则需要我们在实践中不断学习和进步。
通过阅读本文,可以掌握如下信息:
了解 Python 爬虫的一般流程和思路,掌握模拟真人访问、内容捕捉和翻页的基本用法。
了解使用 Python 进行基本数据内容提取、分析的用法。
⭕ 本文爬虫数据及 py 文件下载地址:
Note:产生如下推文列表的 Stata 命令为:
lianxh 爬虫
安装最新版lianxh
命令:
ssc install lianxh, replace
免费公开课
最新课程-直播课
专题 | 嘉宾 | 直播/回看视频 |
---|---|---|
⭐ 最新专题 | 文本分析、机器学习、效率专题、生存分析等 | |
研究设计 | 连玉君 | 我的特斯拉-实证研究设计,-幻灯片- |
面板模型 | 连玉君 | 动态面板模型,-幻灯片- |
面板模型 | 连玉君 | 直击面板数据模型 [免费公开课,2小时] |
⛳ 课程主页
⛳ 课程主页
关于我们
课程, 直播, 视频, 客服, 模型设定, 研究设计, stata, plus, 绘图, 编程, 面板, 论文重现, 可视化, RDD, DID, PSM, 合成控制法
等
连享会小程序:扫一扫,看推文,看视频……
扫码加入连享会微信群,提问交流更方便
✏ 连享会-常见问题解答:
✨ https://gitee.com/lianxh/Course/wikis
New!
lianxh
命令发布了:
随时搜索连享会推文、Stata 资源,安装命令如下:
. ssc install lianxh
使用详情参见帮助文件 (有惊喜):
. help lianxh