Web of Science爬虫 & Python | Selenium

Web of Science爬虫 & Python | Selenium

这个爬虫的思路继承和脱胎自CNKI知网爬虫 & Python

在此前知网爬虫中,只需要运行脚本,selenium就会全自动的输入关键词等信息而进行检索。这个流水线看起来很方便,但是也限制了检索方式。包括此后也有读者私信我问我怎么用其他方式检索或者能不能爬取其他数据。

综上所述,这个爬虫有了以下提升:

  • 检索方式:能自己手动进行任意方式检索,只要最后能进入论文页面
  • 速度:使用BeautifulSoup对网页html处理为爬取数据,相比Selenium对每个数据抓取快得多
  • 稳定性:Web of Science在国内访问不稳定,因为[方式]的改变,可以在网页出现问题后手动解决,也可以从自定义页面开始爬取

思路

新版Web of Science并不像知网一样不需要账号也能检索,而Web of Science一般需要购买账号或者使用图书馆数据库转跳。因为多种多样的的进入方式只能通过手动处理,进而思考:我们是否可以中断程序手动控制浏览器,之后再把控制权交给程序进行?然后就有了这样的代码。

实现

这个程序的主要逻辑是利用Selenium库自动化爬取Web of Science(WOS)网站上的论文详情信息,并将抓取的数据存储到一个CSV文件中。

  1. 定义parse_html函数,接收一个HTML字符串作为参数。该函数通过BeautifulSoup解析HTML内容,提取出论文的相关信息(如标题、引用次数、地址/国家、期刊名等),并将这些信息存入一个字典中。同时,它还尝试从HTML中获取当前论文所在的页码。

  2. 在主函数__main__部分:

    • 初始化Chrome浏览器驱动并打开指定的URL(Web of Science的基础搜索页面)。
    • 要求用户手动操作浏览器至论文详情页面。
    • 切换到论文详情页面对应的浏览器窗口。
    • 进入循环,每次循环处理一篇论文的详情页面:
      • 使用Selenium的WebDriverWait等待页面元素加载完成。
      • 获取页面的HTML源代码并传给parse_html函数处理,得到论文信息字典以及当前页码。
      • 将论文信息添加到一个pandas DataFrame中,如果索引已存在则更新数据,否则新增一行。
      • 每处理完一篇论文后,就将其数据保存到CSV文件中。
      • 尝试点击“下一页”按钮以滚动到下一篇文章的详情页面,若点击失败则提示用户手动解决可能的问题。
      • 添加暂停时间以等待页面加载。
  3. 循环结束后,关闭浏览器驱动。

整个程序实现了自动遍历Web of Science论文详情页面,抽取关键信息,并将结果有序地保存在CSV文件中。

# -*- coding: utf-8 -*-

"""
File: WOS_spider.py
Author: Dramwig
Email: dramwig@outlook.com
Date: 2024-02-27
Version: 1.2

Description: This script uses Selenium and BeautifulSoup to scrape detailed paper information from Web of Science (WOS) website.
It navigates through each paper's detail page, extracts key information such as title, citation count, country, journal, etc., 
and saves the collected data into a CSV file.

Please note that this script is intended for educational purposes only, and you should abide by the terms of service and usage policies 
of the Web of Science when using it or any derivative work.

"""

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium import webdriver
from bs4 import BeautifulSoup
import pandas as pd
import time

# 解析html
def parse_html(html):
    soup = BeautifulSoup(html, 'html.parser')
    
    # 创建一个空的字典
    data_dict = {}
    
    try:
        containers = soup.find_all('div', class_='cdx-two-column-grid-container')
        for container in containers:
            # 在这个容器内找到所有的标签和数据
            labels = container.find_all(class_='cdx-grid-label')
            datas = container.find_all(class_='cdx-grid-data')
            label = labels[0].text.strip()
            data_texts = [data.text.strip() for data in datas] # 提取数据列表中的文本
            text = '\n'.join(data_texts) # 将文本连接成一个字符串,使用换行符分隔
            
            # 存储到字典中
            data_dict[label] = text
    except:
        print(f"解析容器失败")
    
    try:
        class_title = soup.find(class_="title text--large cdx-title")
        data_dict['title'] = class_title.text.strip()
    except:
        print("获取标题失败")

    try:
        class_citation = soup.find(class_="mat-tooltip-trigger medium-link-number link ng-star-inserted")
        data_dict['citation'] = class_citation.text.strip()
    except:
        data_dict['citation'] = '0'

    try:
        class_addresses = soup.find('span', class_='ng-star-inserted', id='FRAOrgTa-RepAddressFull-0')
        print(class_addresses.text.strip())
        data_dict['country'] = class_addresses.text.split(',')[-1].strip()
    except:
        print("获取国家失败")
    
    try:
        class_journal = soup.find(class_="mat-focus-indicator mat-tooltip-trigger font-size-14 summary-source-title-link remove-space no-left-padding mat-button mat-button-base mat-primary font-size-16 ng-star-inserted")
        data_dict['journal'] = class_journal.text.strip()
    except:
        print("获取期刊失败")
    
    try:
        input_box = soup.find(class_='wos-input-underline page-box')  # 获取包含输入框的标签
        index = int(input_box['aria-label'].split()[-1])
    except:
        print("获取页码失败")
        
    return index, data_dict    
        

if __name__ == "__main__": 
    # 0000391627
    # adolescent depression 1: https://webofscience-clarivate-cn-s.era.lib.swjtu.edu.cn/wos/alldb/full-record/WOS:000653016400005 
    url_root = 'https://webofscience-clarivate-cn-s.era.lib.swjtu.edu.cn/wos/alldb/basic-search'
    papers_need = 100000
    file_path = 'result.csv'    
    wait_time = 30
    pause_time = 4
    
    # 变量
    xpath_nextpaper = '/html/body/app-wos/main/div/div/div[2]/div/div/div[2]/app-input-route/app-full-record-home/div[1]/app-page-controls/div/form/div/button[2]'
    df = pd.DataFrame()
    index = 0
    
    # 读取df
    ifread = input("是否读取已有的CSV文件?(y/n)")
    if ifread == 'y':
        df = pd.read_csv(file_path, index_col=0)
        index = int(df.index[-1].split('_')[-1])
        print(f"读取已有的CSV文件,当前行索引为{index},即第{index+1}篇论文")
    
    # 打开的页面
    driver = webdriver.Chrome()
    driver.get(url_root)
    
    # 手动操作,比如切换标签页等
    input("请手动操作至论文详情页面,完成后按Enter键继续...")
    
    # 获取获取当前所有窗口的句柄
    window_handles = driver.window_handles
    
    # 假设新窗口是最后被打开的
    new_window_handle = window_handles[-1]

    # 切换到新窗口
    driver.switch_to.window(new_window_handle)

    # 在新窗口上进行操作,例如获取新窗口的标题
    print("新窗口的标题(请确保页面正确):", driver.title)

    while index <= papers_need:
        print("正在处理第", index+1, "篇论文")
        
        # 等待页面加载
        time.sleep(pause_time/2)
        
        try:
            # 或者等待直到某个元素可见
            element = WebDriverWait(driver, wait_time).until(
                EC.visibility_of_element_located((By.XPATH, '//*[@id="FRAOrgTa-RepAddressFull-0"]'))
            )
        except Exception as e:
            print("An error occurred:", e)
            print("等待超时,可能是页面加载失败")

        time.sleep(pause_time/2)
                
        # 使用Selenium获取页面的HTML源码
        html = driver.page_source
        
        # 解析HTML
        try:
            index,data = parse_html(html)
            row_index = f'Row_{index}'
            if row_index in df.index:
                df.loc[row_index] = pd.Series(data, name=row_index) # 如果行索引存在,则覆盖对应行的数据
            else:
                df = df._append(pd.Series(data, name=row_index)) # 如果行索引不存在,则追加新的行
            df.to_csv(file_path, index=True)  # 将DataFrame保存为CSV文件,保留行索引作为第一列

            # debug
            # input("完成后按Enter键继续...")
        
            # 切换到下一页
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.XPATH, xpath_nextpaper))).click()
        except Exception as e:
            print("An error occurred:", e)
            input("网页出现问题等待手动解决...")
        

    # 关闭浏览器
    driver.quit()

使用

环境

运行方式:脚本运行

本地环境:Python 3.11.5,selenium 4.15.2,pandas 2.0.3,beautifulsoup4 4.12.2

可变参数

url_root = 'https://webofscience-clarivate-cn-s.era.lib.swjtu.edu.cn/wos/alldb/basic-search'
papers_need = 100000
file_path = 'result.csv'    
wait_time = 30
pause_time = 3
  • url_root:是自动打开的网页(可以是任意网页,因为要手动操作)
  • papers_need:自动停止爬取的页数
  • file_path:爬取结果表格文件存储路径
  • wait_time:等待网页某元素加载的时间(秒),可以缩短
  • pause_time:每次翻页后的等待时间(秒),平衡速度和稳定性,可以缩短

使用步骤

  1. 运行程序。会自动打开谷歌浏览器,(确保安装了谷歌chromedriver,可以参考Selenium安装WebDriver最新Chrome驱动

  2. 手动操作。

    1. 进入Web of Science(新版界面),使用自己的方式进入(没有可以淘宝买账号)
    2. 通过自己想要的方式,选择想要的数据库,输入关键词,点击检索
    3. 点击第一篇文章,进入文章页面
  3. 自动爬取。在程序终端输入任意键继续,程序会自动爬取页面信息,并存储在csv表格中。

  4. *异常处理:网页访问异常,点击“后退”回到上个界面,再输入任意键继续,继续爬取。

代码已经托管在Github,欢迎对该项目进行贡献!- Dramwig/WOS-spider: Python script to crawl Web of Science retrieved papers based on selenium package. )