今天使用python来爬取百度学术的论文信息,并且增加了简单的可视化功能,主要内容有:
爬取思路:
主要使用了requests库、bs4里面的BeautifulSoup和re模块
在这一部分先弄清楚准备爬取的网页以及需要在网页上寻找哪些信息!
百度学术的网址为:https://xueshu.baidu.com
进行高级检索时(假设只使用三个信息进行检索):检索词+作者+机构名称
url可以写为:https://xueshu.baidu.com/s?wd=关键词+author作者+affs%3A%28机构名称%29 (这个东西怎么得到的?很简单)
1) 进入百度学术页面
首先打开百度学术的页面,发现最上面的网址是https://xueshu.baidu.com。
2)进入检索后的页面
然后我们选择高级检索,在全部检索词、作者、机构三个位置分别输入图像重建、陈达、南京航空航天大学,如下图所示:
3) 点击搜索后会跳转页面,如下图所示,关注一下最上面的https://那一行,观察一下和最开始https://xueshu.baidu.com的区别,可以发现就是在后面加了一点内容,可以换几个检索词试试,很快就能找到规律,https://xueshu.baidu.com/s?wd=关键词+author作者+affs%3A%28机构名称%29,也可以自己把这一串东西里面的关键词、作者、机构改了,然后用浏览器搜索,试试能不能跳转到百度学术的页面。
我们需要爬取的页面是 https://xueshu.baidu.com/s?wd=关键词+author作者+affs%3A%28机构名称%29 这个,而不是https://xueshu.baidu.com/s。
4)输入检索词后跳转的页面有很多论文,然后我们需要点击标题,进入每一个论文的界面,如果是使用爬虫的话,我们就需要知道每一个论文页面的链接是什么
在上一个图的界面中,可以使用快捷键(Ctrl+Shift+I),然后出现下面这个图的界面。
然后可以在右边找到下面红色框里的这个,在这个页面要做的就是找到每一个论文页面的链接,可以使用下面的方法获取每一个论文页面的链接,其中content是使用requests.get().text()返回的网页内容。
# 获取每一个文章的链接 soup = BeautifulSoup(markup=content, features='lxml') urls_list = soup.find_all('a', href=re.compile('^//xueshu.baidu.com/usercenter/paper'))
通过上一步得到的链接(需要在前面加上https:),我们可以进入每一个论文的界面,如下所示:
我们需要从界面中爬取的信息有:标题、作者、摘要、关键词、DOI、被引量、年份。同样使用Ctrl+Shift+I进入开发者工具,选择元素一项,然后在里面找到要爬取的信息,如需要寻找标题,使用正则表达式对content进行匹配即可,具体操作后续再讲。
B站上有个爬虫实战的教程,把爬虫的流程讲的很详细:https://www.bilibili.com/video/BV12E411A7ZQ?p=16
累了…
先直接贴出代码吧,后续再进行分析。
主要分为3个模块,一个是爬虫模块,一个是构建GUI模块,一个是数据可视化模块。爬虫模块中导入了GUI模块和可视化模块,在爬虫模块中运行三个模块。
(一)爬虫模块代码
# -*- codeing = utf-8 -*- # @Time :2021/6/28 22:03 # @Author: wangyuhan # @File :BaiduScholar.py # @Software: PyCharm ''' 一、 检索通式: 百度学术网址:https://xueshu.baidu.com 高级检索:检索词+作者+机构名称 https://xueshu.baidu.com/s?wd=关键词+author作者+affs%3A%28机构名称%29 二、 匹配规则:使用bs4.BeautifulSoup, lxml库进行解析 匹配href=re.compile('^//xueshu.baidu.com') 三、 分析页面:(需要注意:可能有些资料是图书,没有摘要,则跳过) 作者:re.compile('"{\'button_tp\':\'author\'}">(.*?)</a>') 摘要:re.compile('<p class="abstract" data-sign="">(.*?)</p>') 关键词:re.compile('target="_blank" class="">(.*?)</a></span>') DOI: re.compile('data-click="{\'button_tp\':\'doi\'}">\s+(.*?)\s+</p>') 被引量:re.compile('"{\'button_tp\':\'sc_cited\'}">\s+(\d+)\s+</a>') 年份:re.compile('<p class="kw_main" data-click="{\'button_tp\':\'year\'}">\s+(\d+)\s+</p>') 标题:re.compile('"{\'act_block\':\'main\',\'button_tp\':\'title\'}"\s+>\s+(\S+)\s+</a>') 四、 不爬取图书,若匹配不到摘要,则放弃该文献 爬虫思路: 1、爬取网页 2、解析数据 3、保存数据 ''' from bs4 import BeautifulSoup import re import requests import os import pandas as pd import sys import random import time # 导入自定义模块(GUI组件) import WindowShow import DataVis class Crawler_Paper(): '''百度学术爬虫类''' def __init__(self): self.base_url = 'https://xueshu.baidu.com' self.header = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36"} self.paper_num = 1 self.data = pd.DataFrame() # 用于保存文献信息 def input_message(self, url): ''' 输入检索信息,并且修改url :param url: 待爬网址 :return x_url: 返回修改后的url,进行了关键词、作者和机构检索 ''' try: s = input('是否输入检索词(y/n,不区分大小写)>') if s == 'y' or s =='Y': keyword = input('检索词>') x_url = url + '/s?wd=' + keyword elif s !='n' and s != 'N': print('Input Error!') sys.exit('Goodbye!') a = input('是否输入作者(y/n,不区分大小写)>') if a == 'y' or a == 'Y': author = input('作者>') x_url = x_url + 'author' + author elif a !='n' and a != 'N': print('Input Error!') sys.exit('Goodbye!') af = input('是否输入机构(y/n,不区分大小写)>') if af == 'y' or af =='Y': affs = input('机构>') x_url = x_url + 'affs%3A%28' + affs + '%29' elif af != 'n' and af != 'N': print('Input Error!') sys.exit('Goodbye!') # 返回修改后的url return x_url # 增强程序的容错率,若没有输入,则退出程序 except: print('No input!') sys.exit('Goodbye!') def get_page(self, url, params=None): ''' 获取网页信息函数,发送请求,获取响应内容 :param url: 需要进入的网址 :return req.text: 返回解码后的网页信息 ''' # 休眠,避免被对方反爬到 time.sleep(random.randint(1,5)) req = requests.get(url, headers=self.header, params=params) # 返回解码后的内容:requests.get().text # 返回字节:requests.get().content return req.text def analyze_paper(self, url): ''' 对每一个文章的网页进行检索,获取信息 :param url: 需要检索的文章的url :return: msg: 包含文章的标题、作者、DOI、摘要、被引量、年份、关键词信息,若返回None,则表示不为期刊论文,跳过该文献 ''' content = self.get_page(url) # 匹配结果均为一个列表,关键词、作者、需要保留为List,其余需要转换为str # 关键词 keyword = re.findall('target="_blank" class="">(.*?)</a></span>', content) if not keyword: print('不为论文...') # 如果检索的不是期刊论文,则跳过 return None msg = dict(keyword=keyword) # 文章标题 title = re.findall('"{\'act_block\':\'main\',\'button_tp\':\'title\'}"\s+>\s+(\S+)\s+</a>', content) # 作者 author = re.findall('"{\'button_tp\':\'author\'}">(.*?)</a>', content) # 摘要 abstract = re.findall('<p class="abstract" data-sign="">(.*?)</p>', content) # DOI DOI = re.findall('data-click="{\'button_tp\':\'doi\'}">\s+(.*?)\s+</p>', content) # 被引量 f = re.findall('"{\'button_tp\':\'sc_cited\'}">\s+(\d+)\s+</a>', content) # 年份 pub_time = re.findall('<p class="kw_main" data-click="{\'button_tp\':\'year\'}">\s+(\d+)\s+</p>', content) # 更新字典 # 使用try处理异常,可能有些页面会有信息缺失 print(author) try: msg.update({'title': title[0], 'author': author, 'abstract': abstract[0], 'DOI': DOI[0], 'f': f[0], 'time': pub_time[0] }) except IndexError: Ti = None if title==[] else title[0] Au = None if author==[] else author[0] Ab = None if abstract==[] else abstract[0] Doi = None if DOI==[] else DOI[0] fac = None if f==[] else f[0] Pt = None if pub_time==[] else pub_time[0] msg.update({'title': Ti, 'author': Au, 'abstract': Ab, 'DOI': Doi, 'f': fac, 'time': Pt }) return msg # 核心函数 def analyze_page(self, content): ''' 解析网页信息 :param content: 获取的网页信息(解码后的) ''' # 获取每一个文章的链接 soup = BeautifulSoup(markup=content, features='lxml') urls_list = soup.find_all('a', href=re.compile('^//xueshu.baidu.com/usercenter/paper')) # 对该页面所有文章链接进行迭代 for url in urls_list: message = self.analyze_paper('https:'+url['href']) # 获取文章页面的信息 if message: '''保存数据''' print(f'第{self.paper_num}篇文章爬取中') self.save_data(message) # 保存文献信息 self.paper_num += 1 else: continue # 进行下一页爬取,判断是否还存在下一页的链接 next_urls = re.findall('<a href="(.*?)" class="n" style="margin-right', content) # 若存在下一页内容,则继续爬取 if next_urls: next_url = self.base_url + next_urls[0] content = self.get_page(next_url) self.analyze_page(content) else: print('爬虫结束...') def save_data(self, msg): ''' 保存数据,包括文章标题、作者、关键词、被引量、摘要、年份、DOI :param msg: 有七个键值对的字典 ''' MSG = {'title':msg['title'], 'author':str(['author']), 'keyword':str(['keyword']), 'f':msg['f'], 'time':msg['time'], 'DOI':msg['DOI'], 'abstract':msg['abstract'] } # 重新组织字典内顺序 self.data = self.data.append(pd.DataFrame([MSG])) def start(self): '''执行爬虫''' print('爬虫开始...') # 创建存储信息的文件夹 try: os.makedirs('./BaiduScholar/') except FileExistsError: # 如果文件夹已经存在,则跳过 pass new_url = self.input_message(self.base_url) content = self.get_page(url=new_url) self.analyze_page(content) self.data.to_excel('./BaiduScholar/BaiduScholar.xlsx', index=False) if __name__=='__main__': '''调用自定义爬虫类:Crawler_Paper''' crawler = Crawler_Paper() crawler.start() '''对爬取数据进行窗口可视化''' WindowShow.MainForm() # 窗口显示 DataVis.main() # 可视化数据
(二)GUI模块代码
# -*- codeing = utf-8 -*- # @Time :2021/6/29 14:12 # @Author:wangyuhan # @File :WindowShow.py # @Software: PyCharm import tkinter import tkinter.ttk import pandas as pd import PIL.Image import PIL.ImageTk class MainForm(): def __init__(self, datafile='./BaiduScholar/BaiduScholar.xlsx', image_path='./BaiduScholar/miku.png'): self.DataFile = datafile self.Image_path = image_path self.paper_dict = {} # 文章内容 self.root = tkinter.Tk() # 创建窗体 self.root.title('wangyuhan的程序') self.root.geometry("1200x290") # 设置窗体尺寸 self.creat_init_list() # 初始化列表 self.load_data() # 加载数据 self.root.mainloop() # 循环监听 def creat_init_list(self): self.treeview = tkinter.ttk.Treeview(self.root, columns=('number', 'title', 'author', 'keyword', 'f', 'time', 'DOI', 'abstract'), show="headings", height=20) # 创建普通列表 # 配置列 self.treeview.column('number', width=4, anchor=tkinter.CENTER) self.treeview.column('title', width=120, anchor=tkinter.W) # anchor: 文字在cell里的对齐方式 self.treeview.column('author', width=60, anchor=tkinter.W) self.treeview.column('keyword', width=60, anchor=tkinter.CENTER) self.treeview.column('f', width=5, anchor=tkinter.CENTER) self.treeview.column('time', width=10, anchor=tkinter.CENTER) self.treeview.column('DOI', width=30, anchor=tkinter.CENTER) self.treeview.column('abstract', width=220, anchor=tkinter.W) # 设置标题 self.treeview.heading(column='number', text='序号') self.treeview.heading(column='title', text='标题') self.treeview.heading(column='author', text='作者') self.treeview.heading(column='keyword', text='关键词') self.treeview.heading(column='f', text='被引量') self.treeview.heading(column='time', text='发表年份') self.treeview.heading(column='DOI', text='DOI') self.treeview.heading(column='abstract', text='摘要') ''' 定义滚动条控件 orient为滚动条的方向,vertical:纵向,horizontal:横向 command = self.tree.yview 将滚动条绑定到treeview控件的Y轴 ''' self.scrollbar = tkinter.Scrollbar(self.root, orient='vertical', command=self.treeview.yview) # 滚动条显示 self.scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) # 配置滚动条 # self.scrollbar.config(command=) self.treeview.configure(yscrollcommand=self.scrollbar.set) self.treeview.bind("<Double-Button-1>", self.paper_item_show) # 绑定事件 self.treeview.pack(fill=tkinter.BOTH) # 显示组件 def load_data(self): # 创建下拉列表框 self.frame = tkinter.Frame(self.root) data = pd.read_excel(self.DataFile) len = data.shape[0] # 返回data的行数 for i in range(len): number = str(i+1) title = data.loc[i, 'title'] author = data.loc[i, 'author'] keyword = data.loc[i, 'keyword'] f = data.loc[i, 'f'] pub_time = int(data.loc[i, 'time']) if data.loc[i, 'time']>0 else data.loc[i, 'time'] DOI = data.loc[i, 'DOI'] abstract = data.loc[i, 'abstract'] self.treeview.insert(parent='', index=tkinter.END, values=(number, title, author, keyword, f, pub_time, DOI, abstract)) def paper_item_show(self, event): # 显示文章详细信息 for index in self.treeview.selection(): values = self.treeview.item(index, "values") # 获得选定内容 # 规范年份信息,如将2016.0转换为2016 self.subroot = tkinter.Toplevel() # 创建子窗体 self.subroot.title('wangyuhan的小窗口') # 设置子窗体标题 self.subroot.geometry('800x400') # 设置子窗体尺寸 # picture = PIL.ImageTk.PhotoImage(image=PIL.Image.open(Image_path), size='0.01x0.01') # 加载图片 picture = PIL.ImageTk.PhotoImage(image=PIL.Image.open(self.Image_path).resize((290, 380))) # 加载图片 label_picture = tkinter.Label(self.subroot, image=picture) label_picture.grid(row=0, column=0) # 显示图片 sub_frame = tkinter.Frame(self.subroot) # 定义子Frame # 设置标签 tkinter.Label(sub_frame, text='【标题】' + values[1], font=('微软雅黑', 12), justify='left').grid(row=0, sticky=tkinter.W) tkinter.Label(sub_frame, text='【作者】' + values[2], font=('微软雅黑', 12), justify='left').grid(row=1, sticky=tkinter.W) tkinter.Label(sub_frame, text='【关键词】' + values[3], font=('微软雅黑', 12), justify='left').grid(row=2, sticky=tkinter.W) tkinter.Label(sub_frame, text='【被引量】' + values[4], font=('微软雅黑', 12), justify='left').grid(row=3, sticky=tkinter.W) tkinter.Label(sub_frame, text='【年份】' + values[5], font=('微软雅黑', 12), justify='left').grid(row=4, sticky=tkinter.W) tkinter.Label(sub_frame, text='【DOI】' + values[6], font=('微软雅黑', 12), justify='left').grid(row=5, sticky=tkinter.W) tkinter.Label(sub_frame, text='【摘要】', font=('微软雅黑', 12), justify='left').grid(row=6, sticky=tkinter.W) # 创建一个frame abstract_frame = tkinter.Frame(sub_frame) abstract_listbox = tkinter.Listbox(abstract_frame, height=10, width=50) # 创建Listbox # 需要设置34个中文字符换行 Length = len(values[7].split('/')) list_item = list(values[7]) text = [''] num = 0 Index = 0 for i in list_item: if (Index+1) % 30 == 0: num += 1 text.append(i) else: text[num] = text[num]+i Index += 1 for item in text: abstract_listbox.insert(tkinter.END, item) abstract_scrollbar = tkinter.Scrollbar(abstract_frame) abstract_scrollbar.config(command=abstract_listbox.yview) abstract_scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y) abstract_listbox.pack() abstract_frame.grid(row=7, sticky=tkinter.W) # 显示Frame sub_frame.grid(row=0, column=1, sticky=tkinter.N) self.subroot.mainloop() # 显示子窗体
(三)可视化模块代码
# -*- codeing = utf-8 -*- # @Time :2021/6/30 10:01 # @Author:汪煜晗 # @File :DataVis.py # @Software: PyCharm import numpy as np import pandas as pd import wordcloud import stylecloud from collections import Counter import matplotlib.pyplot as plt def DataProcess(path='./BaiduScholar/BaiduScholar.xlsx'): ''' 提取时间信息和关键词,返回时间信息格式为np.array,关键词信息格式为list ''' # 读取数据 read_data = pd.read_excel(path) keyword = [] for item in read_data['keyword']: # 读取关键词信息 k = str(item) # 规范关键词格式 k = k.replace('[', '') k = k.replace(']', '') k = k.replace('\'', '') k = k.replace(';', '、') if ';' in k else k.replace(',', '、') if ',' in k else k k = k.split('、') if k[-1] == '': del k[-1] keyword.append(k) # 规范时间信息 t = read_data['time'] # 读取时间信息 t = t.dropna(axis=0, how='all') # 清除Nan time = np.asarray(t) return keyword, time def cloud_show(KW, save_path='./BaiduScholar/stylecloud.png'): result = {} for item in KW: for words in item: word = words.strip() print(word) if len(word) == 1: # 单个字不记录 continue else: result[word] = result.get(word, '') + ' ' + word # 重复的词语累加统计量 result_text = " ".join(result.values()) # 获取全部词汇 stylecloud.gen_stylecloud(text=result_text, collocations=False, font_path=r'C:\Windows\Fonts\STXINGKA.TTF', icon_name='fas fa-dog', size=600, output_name=save_path ) # cloud = wordcloud.WordCloud( # # 设置字体路径、背景色、宽度、高度 # font_path=r'C:\Windows\Fonts\STXINGKA.TTF', # background_color='white', width=1000, height=380 # ) # cloud.generate(result_text) # 加载处理文本 # cloud.to_file(save_path) # 保存处理结果 def time_show(TM, save_path='./BaiduScholar/time.png'): # 统计个时间文章数量 num = pd.DataFrame([Counter(TM)]) # 将统计结果的字典类型转换为DataFrame num.sort_index(axis=1, ascending=True, inplace=True) # 对列索引进行升序 # 可视化 fig, ax = plt.subplots() # ggplot (a popular plotting package for R). plt.style.use('ggplot') TimeLabel = num.columns.values.tolist() TimeLabel = list(map(int, TimeLabel)) y_num = np.arange(len(TimeLabel)) timedata = num.iloc[0,:].values p1 = ax.barh(y_num, timedata, align='center', color=list(plt.rcParams['axes.prop_cycle'])[2]['color']) ax.set_yticks(y_num) ax.set_yticklabels(TimeLabel) ax.invert_yaxis() # labels read top-to-bottom # 增加标签 # ax.bar_label(p1, padding=3) ax.set_xlabel('number') ax.set_title('The number of articles') plt.savefig(save_path, dpi=800) plt.show() def main(): Keyword, time = DataProcess() cloud_show(KW=Keyword) time_show(TM=time)