2021SC@SDUSC
简介
根据项目进度安排,需要爬取百度学术生成数据集来测试不同模型的性能表现。然而在实际爬取时遇到了例如页面重复、页面无法访问等问题。
页面重复问题描述及解决方案
在爬取结束后发现出现了大量的重复页面,一方面浪费了大量时间,另一方面去重需要花费额外的经历。起初,我尝试将页面的第一篇论文题目作为该页面的唯一标识,通过集合来去重,然而这种方式依然无法避免时间浪费的问题。
通过对于算法和百度文库的进一步分析,问题源头逐渐浮出水面。在最初的爬虫程序中有一个page_num参数,表示期望爬取的页面数量。通过对url中“pn=?”的替换实现了对所有检索的遍历。
next_page = fir_page.replace("pn=10", "pn={:d}".format(i * 10))
然而部分关键词的检索结果达不到page_num的数值,例如搜索“检索”只有68页
当pn=?的数值超过临界值时,百度学术的处理方式是回退到第一页,然后继续爬取,正因上述机制,出现了页面重复的问题。
为了解决该问题,我对页面进行了进一步分析。当页面存在下一页是会由一个右箭头,如图:
可以对页面进行分析来确定是否存在右箭头,如果存在右箭头,那么可以继续遍历,逻辑如下:
next_icon_soup = soup_new.find(id='page')
if next_icon_soup == None:
# 为none时整个页面都为none
print('None_for_next')
continue
next_icon_soup2 = next_icon_soup.find_all('a')[-1].find(class_='c-icon-pager-next')
if next_icon_soup2 == None:
break
然而在解决该问题时我发现在访问某些页面时返回结果是乱码,此时next_icon_soup2一定为None,即便存在下一页也被迫退出。因此我首先获取next_icon_soup2的父元素,如果父元素不存在,那么一定说明返回结果是乱码,将该页忽略。
页面无法访问问题描述及解决方案
正如前文所提到的,在访问某些页面时返回结果可能为乱码,这对去重也造成了一定的困难,只是单纯的忽略会造成大量数据丢失。引起该问题的根源可能是网站的反爬虫机制,因此最初的想法是每次爬取后睡眠1s来避免被发现,然而效果并不理想。
进一步分析,我尝试对无法访问的页面更换爬虫的访问头,更换后正常访问。基于此,我采取了如下机制:首先使用请求头A进行爬取,如果返回为乱码,更换请求头B,如果仍为乱码再次更换请求头A,因此类推,当更换超过10次仍不能访问后,放弃该页面。采取此机制大多数页面均可正常爬取。
接下来的问题是如何判断爬取的结果为乱码。经过分析,我发现乱码的页面有一个元素类名为“timeout-img”,当页面中出现该元素时,将该页面判定为无法访问的页面。
time_out = soup_new.find(class_='timeout-img')
if time_out_num > 10:
fail_num += 1
flag = False
print('fail...')
break
该部分整体实现如下:
while time_out != None:
time_out_num += 1
time.sleep(1)
print(next_page)
print(' timeout...')
response = requests.get(next_page,headers = headers2)
soup_new = BeautifulSoup(response.text, "lxml")
# 检查是否超时
time_out = soup_new.find(class_='timeout-img')
if time_out_num > 10:
fail_num += 1
flag = False
print('fail...')
break
小结
解决了上述问题后,爬虫正常运行并构建了符合要求的数据集。
运行结果如下(关键词:牛肉):
部分数据集截图如下:
完整代码:
from bs4 import BeautifulSoup
from selenium import webdriver
import time
import pandas as pd
import requests
import re
from collections import defaultdict
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36 Edg/94.0.992.50'}
headers2 = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:93.0) Gecko/20100101 Firefox/93.0'}
def driver_open(key_word):
url = "http://xueshu.baidu.com/"
# driver = webdriver.PhantomJS("D:/phantomjs-2.1.1-windows/bin/phantomjs.exe")
driver = webdriver.Chrome()
driver.get(url)
time.sleep(2)
driver.find_element_by_class_name('s_ipt').send_keys(key_word)
time.sleep(2)
driver.find_element_by_class_name('s_btn_wr').click()
time.sleep(2)
content = driver.page_source.encode('utf-8')
soup = BeautifulSoup(content, 'lxml')
return soup
def page_url_list(soup, page=0):
global soup_new
fir_page = "http://xueshu.baidu.com" + soup.find(id='page').find('a')["href"]
urls_list = []
num = 0
fail_num = 0
for i in range(page):
num+=1
print(i)
# time.sleep(1)
next_page = fir_page.replace("pn=10", "pn={:d}".format(i * 10))
response = requests.get(next_page,headers = headers)
soup_new = BeautifulSoup(response.text, "lxml")
time_out = soup_new.find(class_='timeout-img')
time_out_num = 0
flag = True
while time_out != None:
time_out_num += 1
time.sleep(1)
print(next_page)
print(' timeout...')
response = requests.get(next_page,headers = headers2)
soup_new = BeautifulSoup(response.text, "lxml")
# 检查是否超时
time_out = soup_new.find(class_='timeout-img')
if time_out_num > 10:
fail_num += 1
flag = False
print('fail...')
break
if flag:
c_fonts = soup_new.find_all("h3", class_="t c_font")
for c_font in c_fonts:
url = c_font.find("a")["href"]
urls_list.append(url)
# 如果没有新的页面,停止检索
next_icon_soup = soup_new.find(id='page')
if next_icon_soup == None:
# 为none时整个页面都为none
print('None_for_next')
continue
next_icon_soup2 = next_icon_soup.find_all('a')[-1].find(class_='c-icon-pager-next')
if next_icon_soup2 == None:
break
urls_list = set(urls_list)
print("链接总数量")
print(len(urls_list))
print('失败链接数量:',fail_num*10)
return urls_list
def get_item_info(url):
content_details = requests.get(url,headers = headers)
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 = list(soup.select('div.abstract_wr p.abstract')[0].stripped_strings)[0].replace("\u3000", ' ')
except(IndexError):
abstract = ''
# 提取关键词
try:
key_words = ';'.join(key_word for key_word in list(soup.select('div.dtl_search_word > div')[0].stripped_strings)[1:-1:2])
except(IndexError):
key_words = ''
return title, abstract, key_words
def get_all_data(urls_list):
dit = defaultdict(list)
num = 1
len_list = len(urls_list)
for url in urls_list:
num+=1
print('{}/{}'.format(num,len_list))
title,abstract, key_words = get_item_info(url)
if (len(str(title)) > 0 and len(str(abstract)) > 0 and len(str(key_words)) > 0):
dit["title"].append(title)
dit["abstract"].append(abstract)
dit["key_words"].append(key_words)
return dit
def save_csv(dit,num):
data = pd.DataFrame(dit)
print(data)
columns = ["title", "abstract", "key_words"]
if num == 1:
data.to_csv("data.csv", mode='a',index=False, columns=columns)
else:
data.to_csv("data.csv", mode='a', index=False , header=False)
print("That's OK!")
if __name__ == "__main__":
key_words=['牛肉']
num=1
for key_word in key_words:
soup = driver_open(key_word)
urls_list = page_url_list(soup, page=100)
dit = get_all_data(urls_list)
save_csv(dit,num)
num+=1