输入关键字 抓取所有网页(一个神器之一selenium五年发表文章五年数量)
优采云 发布时间: 2021-11-24 11:06输入关键字 抓取所有网页(一个神器之一selenium五年发表文章五年数量)
PubMed 是一个提供生物医学论文检索和摘要的数据库,可以免费检索。它是生物学中经常使用的文档搜索网站。最近在学习爬虫,包括urllib库、requests库、xpath表达式、scrapy框架等,刚想爬PubMed,作为一个实践,准备爬取过去5年在PubMed上发表的文章文章的数量根据搜索到的关键词,以此为基础来看看这个研究方向近五年的热度。
最初的想法是使用scrapy框架进行爬取和存储。于是打开PubMed,开始分析网页组成、源码等,发现NCBI使用的是动态网页,在进行翻页、关键字搜索等操作时,URL并没有发生变化。于是想到了爬虫神器之一的selenium模块,利用selenium模拟搜索、翻页等操作,然后分析源码,得到想要的信息。由于和selenium接触不多,所以暂时在网上搜索了一些功能。
1、 加载需要的模块
import urllib
import time
import matplotlib.pyplot as plt
import numpy as np
from lxml import etree
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from collections import Counter
2、使用关键字构造URL
手动输入几个关键字来分析 URL 的结构。
PubMed的原链接是``,当你输入搜索'cancer, TCGA, Breast'时,url变成''。所以不难分析,URL由两部分组成,一部分不变,另一部分由我们搜索的关键字串起来的字符串'%2C'组成。因此,对于传入的'keyword',我们可以做如下处理,拼接到搜索后返回的url中
keyword = '%2C'.join(keyword)
tart_url = 'https://www.ncbi.nlm.nih.gov/pubmed/?term='
url = start_url + keyword
这样我们就简单的拼接返回的url。
3、创建浏览器对象
下一步就是使用selenium模块打开我们的Chrome浏览器,跳转到url界面。这个操作相信大家都不陌生。因为后面会进行模拟点击翻页等操作,所以需要实例化一个WebDriverWait对象,方便后续调用
browser = webdriver.Chrome()
self.browser.get(url)
wait = WebDriverWait(browser, 10)
模拟点击网页
打开网页后,我们需要模拟点击网页。首先,我们需要找到过去五年的文章,所以我们需要模拟点击左边的5years按钮,改变单页显示的文章的数量。变成200,可以减少我们的翻页操作
QQ图片246.png
我们要等到网页上加载了相应的元素才可以点击,所以我们需要用到前面提到的WebDriverWait对象。代码显示如下
years =wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#_ds1 > li > ul > li:nth-child(1) > a')))
years.click()
perpage = self.wait.until(EC.element_to_be_clickable((By.XPATH,'//ul[@class="inline_list left display_settings"]/li[3]/a/span[4]')))
perpage.click()
page_200 = self.wait.until(EC.element_to_be_clickable(
(By.CSS_SELECTOR, '#display_settings_menu_ps > fieldset > ul > li:nth-child(6) > label')))
此处使用了 CSS 选择器和 Xpath 表达式。您使用哪一种取决于您的个人喜好。您可以使用任何您觉得方便的方式。EC.element_to_be_clickable 中的输入是一个元组,第一个元素是声明使用哪个选择器,第二个元素是一个表达式。这个函数的意思是等到你在表达式中传入的元素出现在网页上之后,才会在selenium中出现时才会进行后续的操作。在使用selenium的过程中,有很多地方需要用到presence_of_element_located、text_to_be_present_in_element等类似的操作,有兴趣的可以上网查查。
解析网页并提取信息
下一步是解析网页。我使用 lxml 来解析网页并从 Xpath 中提取信息。代码如下
html = self.browser.page_source
doc = etree.HTML(self.html)
self.art_timeanddoi = self.doc.xpath('//div[@class="rprt"]/div[2]/div/p[@class="details"]/text()')
for i in self.art_timeanddoi:
self.yearlist.append(i[2:6])
for i in self.yearlist:
if re.match('2', i):
continue
else:
self.yearlist.remove(i)
这里主要是对网页中的年份进行Xpath提取和字符串切片处理,从而获取年份信息并保存到列表中。在实际操作中发现了少量其他不相关的元素。经过仔细排查,发现是网页源代码有问题。因此,遵循常规过程以删除不以“2”开头的元素。最终结果被检查是正确的。
这里我们提取了单个页面的信息。
翻页操作
我们还需要翻页以获取我们想要的所有信息。硒模块仍在使用。
status = True
def next_page():
try:
self.nextpage = self.wait.until(
EC.element_to_be_clickable((By.XPATH, '//*[@title="Next page of results"]')))
except TimeoutException:
status = False
while True:
if status:
next_page()
else:
break
这里我首先定义了一个函数来判断是否可以翻页,使用异常捕获,因为翻页动作要停在最后一页,不能再次点击翻页元素。因此,在进行点击操作时应先判断。如果无法点击,会因为超时而抛出异常。next_page 函数中的状态变量记录了“下一页”按钮是否可以被点击。接下来是一个 while True 循环。只有 next_page 变为 False 时,翻页动作才会停止。在循环中,我们继续解析网页来提取信息,就可以得到我们想要的所有信息。
可视化
爬取了所有年份信息的列表后,我们还需要进一步的可视化处理来直观的判断目标话题的研究趋势,所以使用matplotlib进行可视化操作,简单的画一个折线图。代码显示如下:
def plot_curve(yearlist):
counter = Counter(yearlist)
dic = dict(counter)
keys = sorted(list(dic.keys()))
curcount = 0
y = []
temp = [int(i) for i in keys]
for i in range(min(temp), max(temp)+1):
if str(i) in keys:
curcount += self.dic[str(i)]
y.append(self.curcount)
else:
y.append(self.curcount)
plt.figure(figsize=(8, 5))
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.xlabel('年份')
plt.ylabel('近五年文章数量')
plt.plot(np.arange(min(temp), max(temp)+1), np.array(y), 'r', marker='+', linewidth=2)
plt.show()
最后贴出完整代码:
import re
import urllib
import time
import numpy as np
from lxml import etree
from selenium import webdriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from collections import Counter
import matplotlib.pyplot as plt
class NcbiInfo(object):
browser = webdriver.Chrome()
start_url = 'https://www.ncbi.nlm.nih.gov/pubmed/?term='
wait = WebDriverWait(browser, 10)
def __init__(self, keywordlist):
self.temp = [urllib.parse.quote(i) for i in keywordlist]
self.keyword = '%2C'.join(self.temp)
self.title = ' AND '.join(self.temp)
self.url = NcbiInfo.start_url + self.keyword
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'}
self.file = open('information.txt', 'w')
self.status = True
self.yearlist = []
def click_yearandabstract(self, ):
self.browser.get(self.url)
years = self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, '#_ds1 > li > ul > li:nth-child(1) > a')))
years.click()
perpage = self.wait.until(EC.element_to_be_clickable((By.XPATH, '//ul[@class="inline_list left display_settings"]/li[3]/a/span[4]')))
perpage.click()
page_200 = self.wait.until(EC.element_to_be_clickable(
(By.CSS_SELECTOR, '#display_settings_menu_ps > fieldset > ul > li:nth-child(6) > label')))
page_200.click()
def get_response(self):
self.html = self.browser.page_source
self.doc = etree.HTML(self.html)
def get_numof_article(self):
article_count_ = self.doc.xpath('//*[@id="maincontent"]/div/div[3]/div[1]/h3/text()')[0]
if 'of' not in article_count_:
print(article_count_.split(': ')[1])
else:
print(article_count_.split('of ')[1])
def get_info(self):
self.art_timeanddoi = self.doc.xpath('//div[@class="rprt"]/div[2]/div/p[@class="details"]/text()')
for i in self.art_timeanddoi:
self.yearlist.append(i[2:6])
for i in self.yearlist:
if re.match('2', i):
continue
else:
self.yearlist.remove(i)
def next_page(self):
try:
self.nextpage = self.wait.until(
EC.element_to_be_clickable((By.XPATH, '//*[@title="Next page of results"]')))
except TimeoutException:
self.status = False
def plot_curve(self):
self.counter = Counter(self.yearlist)
self.dic = dict(self.counter)
self.keys = sorted(list(self.dic.keys()))
self.curcount = 0
self.y = []
temp = [int(i) for i in self.keys]
for i in range(min(temp), max(temp)+1):
if str(i) in self.keys:
self.curcount += self.dic[str(i)]
self.y.append(self.curcount)
else:
self.y.append(self.curcount)
plt.figure(figsize=(8, 5))
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
plt.xlabel('年份')
plt.ylabel('近五年文章数量')
plt.title(self.title)
plt.plot(np.arange(min(temp), max(temp)+1), np.array(self.y), 'r', marker='+', linewidth=2)
plt.show()
def main(self):
self.click_yearandabstract()
time.sleep(3)
self.get_response()
self.get_numof_article()
while True:
self.get_info()
self.next_page()
if self.status:
self.nextpage.click()
self.get_response()
else:
break
self.plot_curve()
if __name__ == '__main__':
a = NcbiInfo(['TCGA', 'breast', 'cancer'])
a.main()
结果如下:
图片.png