Sci-Hub + Python爬虫实现文献批量下载

Sci-Hub

查看原文:Sci-Hub + Python爬虫实现文献批量下载 | Anomie

操作思路

  • 在Web of Science或者其他学术搜索引擎上查找所需要的文献,然后将全体检索结果的信息导出成Excel(包括作者、标题、出版年份、期刊、DOI号等等)

  • 以DOI号为检索条件,到Sci-Hub下载文献,将这一过程写成爬虫进行批量处理

    • 导出DOI号序列,写成循环来逐个爬取

    • 以DOI号检索文献,进入下载页面,查找到保存按钮对应的元素,下载到本地

    • 将下载的PDF文件按照自己的标准重命名

  • 手动补全无法在Sci-Hub上得到的文献

注意事项

自动下载覆盖率

这个方法不可能自动获得100%的文献,因为:

  • Sci-Hub据说对主要出版商的文献覆盖率超过90%,右——这些数据都只是道听途说,总之就是说在批量下载百篇以上文献的时候,肯定会遇到Sci-Hub未收录的情况,实际覆盖率还和学科领域、发表年份、文献语种之类的因素有关(实际操作中发现,Sci-Hub对西班牙语之类的小语种文献覆盖率没有英语文献那么高)

  • Web of Science检索结果不一定包含DOI号(抑或PMID号),这些文章自然也就没无法在Sci-Hub上检索

除了上述硬性的限制,一些文献还可能因为其他原因下载失败:

  • 网络条件不好,下载时间过长

  • 频繁请求导致本机IP被封禁

解决思路如下。

下载时间过长

打开网页之后,点击save按钮就会自动开始下载PDF文件,通常在几秒内就能完成,但偶尔也会遇到下载特别久的情况(比如网卡了)。但是网页端保持打开的时长是确定的,我经过调整设置成了20秒。也就是说,点击save按钮20秒之后,不管文件有没有下载完成,下载过程都会中断。只下了一半的文件就会破损。在使用Chrome Driver的时候,在下载文件夹里可能会残留一个后缀为.crdownload的临时性文件。

对于这些临时性文件,可以通过检查文件名后缀的方式来辨别——只有后缀为.pdf的文件,我们才把它视为下载成功的完整文献,并进入到重命名保存的工作;否则就删除文件并标记为下载失败。

为了减少以上情况出现的概率,需要设置一个足够长的等待时间,具体根据文件大小和网络速度而定。但是也没有必要等得太长,不然也会降低爬虫的运行速度。建议在开始正式运行之前可以先下载两到三个文献尝试一下,然后确定适合的时长。

IP被Sci-Hub封禁

因为高频率重复相同的访问和下载请求,Sci-Hub有可能将我判定为机器人(事实上我也确实是一个机器人),并禁止我的IP访问其网站。想到了3个解决办法:

  1. sleep()控制爬虫的暂停时间,适当延长两次访问之间的时间间隔。但是这样做的代价就是牺牲爬虫的工作速度,需要做好二者的权衡。我这里设置成了5秒。

  2. 切换访问域名。Sci-Hub本身拥有多个域名,目前有https://sci-hub.ruhttps://sci-hub.sthttps://sci-hub.se这三个。在每次访问的时候切换到不同的域名。

  3. 如果用VPN的话,频频被Sci-Hub禁的时候可以换一条线路。(只要我改IP,你就禁不到我)

这个问题也在提醒我们,频繁用爬虫确实会给网站服务器带来过大的负担。我们在用爬虫给自己省力气的同时也还要体谅体谅网站。(呜呜呜不说了,给他们捐钱去了)

重复运行提高覆盖率

在我的实际操作中,网络条件良好的情况下,第一次全部爬取,得到了60%左右的文献;

第二次针对缺失的文献爬取,将文献覆盖率提高到了80%左右;

第三次爬取之后,覆盖率接近90%,可以确定剩下的文献都是Sci-Hub没有收录的,或者在检索的时DOI号就缺失的。对于这些文献,只能写一段代码输出其DOI号或者文章标题,手动补全。

整体代码

import pandas as pd
from selenium import webdriver
import time
import os
import random
table = pd.read_excel('Full record.xlsx',sheet_name='savedrecs')

dois = table['DOI'].copy(deep=True)
for i in dois.index:
    dois[i] = str(dois[i])
finished = [0 for i in range(len(dois))]

auths = table['Authors'].copy(deep=True)
for i in auths.index:
    name = auths[i].split(';')[0]
    auths[i] = name.split(',')[0]

years = table['Publication Year']
def scihub_get(dois, i):
    chromeOptions = webdriver.ChromeOptions()
    prefs = {"download.default_directory" : "D:/Chrome Downloads/Articles"}
    chromeOptions.add_experimental_option("prefs",prefs)
    chrome_driver_path = "D:/Chrome Downloads/chromedriver.exe"
    wd = webdriver.Chrome(executable_path=chrome_driver_path) #, options=chromeOptions)

    scihub = ['https://sci-hub.ru/', 'https://sci-hub.st/', 'https://sci-hub.se/']
    root = scihub[random.randint(0,2)]
    
    # search by doi
    doi = dois[i]
    wd.get(root+doi)
    time.sleep(1)
    
    try:
        b = wd.find_element_by_xpath('//*[@id="buttons"]/button')
        b.click()
        flag = True
        time.sleep(20)
    except:
        print('access failed.    index = '+str(i)+'    doi = '+doi)
        flag = False
        time.sleep(5)
    
    wd.quit()
    return flag
def rename_file(dois, finished, auths, years, i):
    time.sleep(1)
    path = 'C:/Users/86158/Downloads/'
    dir_list = os.listdir(path)
    if len(dir_list)>0:
        found = 0
        for file in dir_list:
            if file[0:3]!='No_':
                found = 1
                break

        if found==0: #when didn't get the "save" button
            print('download failed.    index = '+str(i)+'    doi = '+dois[i])
        else: #when we get a new file
            l = file.split('.')
            if l[len(l)-1] != "pdf": #when the file was half downloaded
                print('download incomplete.    index = '+str(i)+'    doi = '+dois[i])
            else:
                old = path+ file
                auth = str(auths[i])
                try:
                    year = str(int(years[i]))
                except:
                    year = str(years[i])
                index = [str(i//100), str((i%100)//10), str(i%10)]
                    
                new = path+ 'No_' +index[0]+index[1]+index[2]+'_' +auth+'_'+year+'.pdf'
                os.rename(old, new)
                finished[i] = 1
def article_get(dois, finished, auths, years, i):
    # visit scihub
    if dois[i]=='nan':
        print('doi missing.    index = '+str(i))
    else:
        if scihub_get(dois, i):
            # rename
            rename_file(dois, finished, auths, years, i)
for i in range(0,3): #len(dois)):
    article_get(dois, finished, auths, years, i)
# try a new round for missing articles
path = 'C:/Users/86158/Downloads/'
dir_list = os.listdir(path)
for i in range(0,len(dois)):
    auth = str(auths[i])
    try:
        year = str(int(years[i]))
    except:
        year = str(years[i])
    index = [str(i//100), str((i%100)//10), str(i%10)]
    filename = 'No_' +index[0]+index[1]+index[2]+'_' +auth+'_'+year+'.pdf'
    
    if filename in dir_list:
        continue
    else:    
        article_get(dois, finished, auths, years, i)
# output information of all missing articles
path = 'C:/Users/86158/Downloads/'
dir_list = os.listdir(path)
count = 0
for i in range(0,len(dois)):
    auth = str(auths[i])
    try:
        year = str(int(years[i]))
    except:
        year = str(years[i])
    index = [str(i//100), str((i%100)//10), str(i%10)]
    filename = 'No_' +index[0]+index[1]+index[2]+'_' +auth+'_'+year+'.pdf'
    
    if filename in dir_list:
        continue
    else:    
        print(filename+"    doi: "+dois[i])
        if dois[i]=="nan":
            print("    title: "+table["Article Title"][i])
        count+=1
print(str(count)+" articles missing in total.")
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容