2021SC@SDUSC
一、爬取百度文库数据的思路
首先,我们需要模拟浏览器访问百度学术,然后搜索相关关键词,将得到的页面传回给bs4进行解析。
随后,我们将获得搜索结果的每一个页面,将页面中的学术文档链接提取出来,然后进行逐一访问
我们需要在每一个访问到的学术文档页面中提取关键词、摘要、标题。最后将它们统一保存在数据库或者硬盘上。
二、源码分析与改进
我们使用了学长提供的python爬虫对百度文库的数据进行了爬取
import pymysql
from bs4 import BeautifulSoup
from selenium import webdriver
import time
import pandas as pd
import requests
from selenium.webdriver.chrome.service import Service
主要使用了上面的一些库:比如beautifulsoup4(主要用于lxml解析),selenium(主要用于模拟人类使用浏览器获取信息), pandas,requests。
我们在运行该文件之前,需要在虚拟环境中先将库的内容安装好,并且要下载自己浏览器对应的driver。在这里我使用的是Chrome,因此我先要下载对应的Chromedriver版本。Chromedriver是由Chromium team开发维护的,他通过Chrome的自动代理框架,达到控制浏览器的作用。
其中最后一行的service是后来基于学长的代码和我自己的python版本情况添加修改的。使用service可以控制Chrome driver的生死,做到用完就关闭。
main
函数入口:
if __name__ == "__main__":
key_word = "AI"
soup = driver_open(key_word)
urls_list = page_url_list_2(soup, maxpage=72)
# get_all_data(urls_list)
save_csv(urls_list = urls_list)
爬虫文件名称为"pabaiduxueshu.py"其内容专为爬取百度文库内容设计。首先key_words设置为爬取内容的关键词,用这个关键词进行搜索。
最后两行有两种保存方式,一种是保存在数据库中,还有一种是保存成csv格式。我选择了后一种。具体的保存方式我将在之后分析。
def driver_open(key_word):
url = "http://xueshu.baidu.com/"
service = Service("/你的地址/chromedriver")
driver = webdriver.Chrome(service=service)
driver.get(url)
time.sleep(10)
driver.find_element_by_class_name('s_ipt').send_keys(key_word)
time.sleep(5)
driver.find_element_by_class_name('s_btn_wr').click()
time.sleep(5)
content = driver.page_source.encode('utf-8')
driver.close()
soup = BeautifulSoup(content, 'lxml')
return soup
这一段代码名叫"driver_open(key_word)",其输入是main中的关键词。它的目的主要是通过模拟人类使用浏览器的方法,将想要搜索的关键词输入到百度文库的搜索框内,在一定的时间之后,点击搜索,并将页面内容按照“utf-8”的格式保存,最后将得到的内容用lxml形式解析。它主要做了这几件事:
- 打开本地的chromedriver
- 模拟人类操作,搜索相关内容
- 保存内容,并关闭Chromedriver
- 将内容按照lxml方式解析成名称为soup的树形结构内容并返回soup。
在这段代码中,我基于学长的代码,做了如下修改:因为我使用的是python3.10版本,这个版本的bs4库需要先使用service来打开Chrome driver。
在模拟浏览器访问的过程中,可以使用time.sleep()休眠一定的时间。防止反爬虫机制将IP封掉。
获取百度学术搜索信息后,我们发现搜索结果并不能在一页中显示完整,百度学术通过多个页来显示信息,而且每个页都有一个url,url的格式也很有规律。现在我们需要访问所有的页,然后逐一提取每一页的学术文档的链接。这里我们用两种方式如下所示:
版本一:
假设我们提前知道了访问的总页数,比如是72页。我们就可以根据每一页URL的格式上的规律(见代码),生成每一页的URL然后进行逐个访问。
#自定义页数
def page_url_list(soup, maxpage=0):
fir_page = ("http://xueshu.baidu.com" + soup.find_all("a", class_="n")[0]["href"])
urls_list = []
fail_list = []
for i in range(maxpage):
this_page = fir_page.replace("pn=10", "pn={:d}".format(i * 10))
response = requests.get(this_page)
soup_new = BeautifulSoup(response.text, "lxml")
c_fonts = soup_new.find_all("h3", class_="t c_font")
for c_font in c_fonts:
url = c_font.find("a").attrs["href"]
urls_list.append(url)
if len(soup_new.find_all("a", class_="n", style="margin-right: 19px;")) == 0:
fail_list.append(this_page)
print("找到第" + str(i + 1) + "页,href中pn值为" + this_page[this_page.index("pn=") + 3 : this_page.index("&tn")])
print(this_page)
print()
print("失败页面")
for i in range(len(fail_list)):
print(fail_list[i])
set(urls_list)
print("链接总数量")
print(len(urls_list))
return urls_list
版本一:
假设不知道搜索结果一共有多少页。我们先通过第一页的页面底部的“下一页”按钮访问下一页,并循环此过程,直到不再出现“下一页”按钮。
#自动停止
def page_url_list_2(soup, maxpage=0):
fir_page = ("http://xueshu.baidu.com" + soup.find_all("a", class_="n")[0]["href"]).replace("pn=10", "pn=0")
next_page = fir_page
urls_list = []
maxtime = 100
for i in range(maxpage):
for j in range(maxtime):
response = requests.get(next_page)
soup_new = BeautifulSoup(response.text, "lxml")
c_fonts = soup_new.find_all("h3", class_="t c_font")
for c_font in c_fonts:
url = c_font.find("a").attrs["href"]
urls_list.append(url)
print("找到第" + str(i + 1) + "页,href中pn值为" + next_page[next_page.index("pn=") + 3 : next_page.index("&tn")])
if len(soup_new.find_all("a", class_="n", style="margin-right: 19px;")) != 0:
break;
print("第" + str(j + 1) + "次访问第" + str(i + 1) + "页未找到下一页...")
if j == maxtime - 1:
print("在第" + str(i + 2) + "页停止")
print()
set(urls_list)
print("链接总数量")
print(len(urls_list))
return urls_list
next_page = "http://xueshu.baidu.com" + soup_new.find_all("a", class_="n", style="margin-right: 19px;")[0]["href"]
print(next_page)
print(soup_new.find_all("a", class_="n", style="margin-right: 19px;"))
print()
set(urls_list)
print("链接总数量")
print(len(urls_list))
return urls_list
上面两个版本的访问页面方式不太相同,一个是通过URL格式的规律来找到每一页的URL;另一种是通过加载新页面后获得的底部的按钮获得下一页的URL。
需要注意的是:在实际操作中,我发现有时候某些页面访问一次会无法正常返回信息。这就会导致在提取信息的过程中某些页面的学术文档链接缺失。
解决这个问题的方法有两个:一是将不能正常访问的百度学术搜索结果页面URL保存起来,之后再次遍历且访问。二是设置一个访问次数上限,比如20次,那么在遇到每个不能正常访问的页面时,我们都继续访问它20次,直到它返回正常信息。
尽管我们有办法解决页面非正常返回的问题,我们也很很难避免频繁访问使得百度将IP封掉。我们可以时常更换IP或者设置每次访问时间间隔(但是这会导致爬取数据时间过长)
def get_item_info(url):
content_details = requests.get(url)
soup = BeautifulSoup(content_details.text, "lxml")
# 提取文章题目
try:
title = ''.join(list(soup.select('#dtl_l > div > h3 > a')[0].stripped_strings))
except(IndexError):
title = ''
try:
abstract = ''.join(list(soup.select('div.abstract_wr p.abstract')[0].stripped_strings)[0].replace("\u3000", ' '))
except(IndexError):
abstract = ''
try:
key_words = ''
soup1 = BeautifulSoup(str(soup.select('#dtl_l > div.main-info > div.c_content > div.kw_wr > p.kw_main')[0]), "lxml")
a_list = soup1.find_all('a')
for a in a_list:
key_words = key_words + str(a.get_text()).replace(";",";") + ";"
except(IndexError):
key_words = ''
return title, abstract, key_words
这一段目的是对每个学术文档的URL进行访问并提取相关的关键词、摘要、标题信息,并将它们返回。
可以看到,这里使用了很对try catch语句,防止访问过程中由于提取不到数据的错误使得程序完全中断。
def save_csv(urls_list):
titles = []
abstracts = []
key_words = []
count = 0
for url in urls_list:
count += 1
print("正在提取第" + str(count) + "篇论文")
title, abstract, key_word = get_item_info(url)
if title == '' or abstract == '' or key_word == '':
continue
titles.append(title)
abstracts.append(abstract)
key_words.append(key_word)
dit = {}
dit["titles"] = titles
dit["abstracts"] = abstracts
dit["key_words"] = key_words
data = pd.DataFrame(dit)
columns = ["titles", "abstracts", "key_words"]
data.to_csv("abstract_data_2.csv", index=False, columns=columns)
print("完成!")
这里将获取的数据转换成Data Frame格式,然后再转换成csv格式保存下来。