Python爬虫: 《经济研究》研究热点和主题分析
优采云 发布时间: 2022-06-18 05:40Python爬虫: 《经济研究》研究热点和主题分析
来源:stata连享会——连玉君老师团队的实证分析经验分享。主页:
经管之家长期征稿邮箱:
0. 背景使用 Python 爬虫的好处
爬虫,顾名思义,指我们沿着网络抓取自己的猎物(数据)。使用这种方法,我们可以向网站发起请求,获取资源后分析并提取有用数据。相比与其他静态编程语言,如 java,c#,C++,python 抓取网页文档的接口更简洁;相比其他动态脚本语言,如 perl,shell,python 的 urllib2 包提供了较为完整的访问网页文档的 API。
此外,抓取网页有时候需要模拟浏览器的行为,譬如模拟用户登陆、模拟 session/cookie 的存储和设置。在 python 里有非常优秀的第三方包帮你搞定,如requests。
抓取的网页通常需要处理,比如过滤 html 标签,提取文本等。python 的beautifulsoap提供了简洁的文档处理功能,能用极短的代码完成大部分文档的处理。
本文使用工具
1)本文分析网页时使用 fildder 进行抓包,配合谷歌浏览器 f12 检查。使用文本 IDE、vscode 编写爬虫代码,requests 库获取网页信息,deautifulsoup 库和正则表达式 re 解析网页,xlwt 写入为 xls 格式。
2)数据分析与可视化工具为 Jupyter Notebook,用 Pandas 库进行数据处理,matplotlib 画图等。
本文目标
本文目标为模拟知网的高级搜索,获取特定学术期刊的历史论文信息。以国内经济学顶刊《经济研究》为例,获取其 2000-2018 年的全部文章数据。具体包括摘要页的题名、作者、来源、发表时间、数据库、被引、下载,以及详细页面的单位、关键字、摘要等信息,以探究近二十年来国内核心期刊发文概况及研究热点的变动趋势。
本文主要由几下三部分构成:
对爬取网页进行分析;爬虫构建;对爬取数据进行处理和简析。
1. 网页分析
本文需要的高级检索页面如下:
高级检索页面
爬虫实现上来看,需要在维持 session 的情况下,多次向服务器发送信息,来获得目标页面。主要有三个要实现的动作:网页(知网)高级检索、打开详细界面与翻页。
上述三个动作的实现,关键在于信息的有效提交和获取。此处有两大难点(这是 Python 爬虫的基本功,可参考北理工嵩天老师MOOC课程学习巩固):
(1) 抓包后,哪些是需要关注的请求?答:本文将主要请求提取出来,知网中的每一个请求都是按照请求方法的作用进行命名,这在很大程度上能够帮助我们快速定位到我们需要的请求。多次尝试后,我们大致定位出每个请求的信息,详情见下页图。
(2) 找到需要关注的请求后,如何构建请求?答:具体来说,每次 get 请求中,我们上传的参数如何设置。根据 fildder 我们可以获取需要上传的参数。这些参数中有很多都是固定的,也有一部分是动态的。动态的一般就是检索条件需要的,需要注意是如果没有规定某个条件,那这个参数是不传递的。下面列出了一部分常用的传递参数,更多参数大家自己通过这个方法查看。至此我们了解的如何获取页面请求以及如何设置搜索参数,下面我们可以开始进行爬虫代码编写。
浏览器正常浏览下,三个动作的抓包如下所示:
浏览器正常浏览下,三个动作的抓包
爬虫 requests 模拟下,三个动作的抓包:
爬虫 requests 模拟下,三个动作的抓包
爬取网页数据需要设置的参数:
需要设置的参数
上图红框中参数信息详情:
参数信息
2. 爬虫构建
第一,需要设置我们的报文信息,模拟真人访问,用于回避网站的审查
def crawl_headers(self):<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> headers = {<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'User-Agent':<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'Host':<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'kns.cnki.net',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'Connection':<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'keep-alive',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'Cache-Control':<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'max-age=0',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> }<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> return headers<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" />
第二,发送请求,并捕捉到我们所需的内容(核心关键步骤)
<p> def search_reference(self, ueser_input):<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> '''<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 第一次发送post请求<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 再一次发送get请求,这次请求没有写文献等东西<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 两次请求来获得文献列表<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> '''<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> #删除了些参数,并改变action赋值,便可完成时间区间的限定<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> static_post_data = {<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'action': '44',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'NaviCode': '*',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'ua': '1.21',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'isinEn': '1',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'PageName': 'ASP.brief_result_aspx',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'DbPrefix': 'SCDB',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'DbCatalog': '中国学术文献网络出版总库',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'ConfigFile': 'SCDB.xml',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'db_opt': 'CJFQ,CDFD,CMFD,CPFD,IPFD,CCND,CCJD', # 搜索类别(CNKI右侧的)<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> 'his': '0',<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> '__': time.asctime(time.localtime()) + ' GMT+0800 (中国标准时间)'<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> }<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> # 将固定字段与自定义字段组合<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> post_data = {**static_post_data, **ueser_input}<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> #print(post_data)<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> # 必须有第一次请求,否则会提示服务器没有用户<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> first_post_res = self.session.post(<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> SEARCH_HANDLE_URL, data=post_data, headers=HEADER)<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> # get请求中需要传入第一个检索条件的值<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> #key_value = quote(ueser_input.get('magazine_value1'))<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> #self.get_result_url = GET_PAGE_URL + first_post_res.text + '&t=1544249384932&keyValue=' + key_value + '&S=1&sorttype='<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> self.get_result_url = GET_PAGE_URL + first_post_res.text + '&t=1562315821144&keyValue=&S=1&sorttype='<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> #print(self.get_result_url)<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> # 检索结果的第一个页面<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> second_get_res = self.session.get(self.get_result_url, headers=HEADER)<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> change_page_pattern_compile = re.compile(<br style="max-width: 100%;box-sizing: border-box !important;word-wrap: break-word !important;" /> r'.*?pagerTitleCell.*?