The Birth of a Data Hoarder

人呐就都不知道,自己就不可以预料。一个人的命运啊,当然要靠自我奋斗,但是也要考虑到历史的行程。我绝对不知道,我作为一个资料室打杂工怎么把我选到其他办公室去搬砖了。最近,我们学院申请一个基金。这个基金申请需要填写我们学院的老师和他们的合作者的一些论文信息(期刊的影响因子、论文的引用数、论文原文之类),这块搬不动的砖头不知怎么就砸到了我身上,罢了,苟利国家身死矣,岂因祸福避驱之。

加起来有 40 多位老师。其中有十几位老师是国外的教授,还有一些老师是大忙人,所以大部分论文是要我自己去找原文的。每一篇论文都需要查询引用数(排除所有原作者的引用数)和影响因子。

一开始我用古典的方法(也即复制一个一个文献,粘贴至 duckduckgo,如果没有找到官网,跳到 google 再来一遍,是的,我根本没用什么专业的学术搜索引擎)找每篇论文的 doi。我打算先保存所有论文 doi,之后再用 sci-hub 迅速下载很大一部分论文,最后当面教育那些不识像的顽固分子。过了不久,我发现这个任务虽然简单,但是太他妈的无趣了。再加上,还有一个问题是,我要找 mathscinet 上的引用的话又要重来一遍类似过程。

据说 geek 面对此等问题是这样的。

repetitive tasks

我发现了一个神奇的网站——ams 的 reference search。用这个可以从参考文献搜索到 bibtex 信息,很多论文搜索到的 bibtex 信息里包含了 doi。其他学科参见 What are good sites to find citations in BibTex format? - TeX - LaTeX Stack Exchange

所以开始写脚本。下面这个叫 mref.py 的 python 脚本是用来获取 bibtex 信息的。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import argparse
import requests
from bs4 import BeautifulSoup
import pyperclip

def get_info(dataType, ref):
    mref_url = 'https://mathscinet.ams.org/mathscinet-mref'
    params = ( ('dataType', dataType),
               ('ref', ref),)
    try:
        response = requests.get(mref_url, params=params)
        soup = BeautifulSoup(response.text, 'html.parser')
        trs = soup.table.find_all('tr')
        trs_texts = [tr.getText() for tr in trs]
        numf = 'No Unique Match Found'
        matched = '* Matched *'
        if numf in trs_texts:
            print(numf, file=sys.stderr)
        elif matched in trs_texts:
            index = trs_texts.index(matched)
            if dataType == 'link':
                # print(trs_texts[index+2])
                print(BeautifulSoup(trs_texts[index+2],
                                    'html.parser').a['href'])
            else:
                print(trs_texts[index+1])
        else:
            print('html structure may have changed', file=sys.stderr)
    except requests.exceptions.RequestException as e:
        print(e, file=sys.stderr)

def main():
    parser = argparse.ArgumentParser(description='Get bibliography from ams mref')
    parser.add_argument('-b', '--bibtex', dest='type',
                        action='append_const', const='bibtex',
                        help='get bibtex, default if no other arguments')
    parser.add_argument('-m', '--mathscinet', dest='type',
                        action='append_const', const='mathscinet',
                        help='get APA format refrence')
    parser.add_argument('-a', '--amsrefs', dest='type',
                        action='append_const', const='amsrefs',
                        help='get amsrefs')
    parser.add_argument('-l', '--link', dest='type',
                        action='append_const', const='link',
                        help='get link to mathematical review')
    parser.add_argument('-c', '--clipboard', action='store_true',
                        default=False, help='whether use system clipboard')
    parser.add_argument('terms', nargs='*', help='terms to search')
    args = vars(parser.parse_args())
    # args = vars(parser.parse_args(['-c', '-m', '-b', '-a', '-l',
    #                                'J. Cheeger and T. Colding, On the structure ' +
    #                                'of space  with Ricci curvature bounded below ' +
    #                                'I , J. Differential Geom. 46 (1997) 406–480.' +
    #                                ' MR1484888', ]))
    terms = args['terms']
    type = args['type'] if args['type'] else ['bibtex']
    if args['clipboard']:
        terms.append(pyperclip.paste())
    if not terms:
        print('\nSearch term missing. Add -c to use clipboard', file=sys.stderr)
        exit(1)
    for ref in terms:
        for dataType in type:
            get_info(dataType, ref)

if __name__== "__main__":
    main()        

之后的所有代码都是 bash 脚本了。先给每位老师新建一个文件夹,复制他们的文献信息后,运行如下命令,便可获取每一篇论文的 bibtex 文件和 mathematical reviews url。

xclip -o > papers.txt
parallel -k -I _ mref.py _ < papers.txt | tee bib.bib
grep -i mrnu bib.bib | perl -pe \
    's|.*\{(.*)\}.*|http://www.ams.org/mathscinet-getitem\?mr=MR\1|g' \
    | tee mrURL.txt

再运行下面的命令保存每篇论文的标题,方便重命名。

perl -pe 'if (not /,$/) {chomp} else {pass}' bib.bib \
    | grep -i -E '^\s*(title|url)' | tr -s " " \
    | perl -pe 's|.*=\s*\{(.*)\},$|\1|g; s|\{(.*?)\}|\1|g; s|\\.||g' \
    > titles.txt

doi 也保存一下。

grep -i url bib.bib | perl -pe 's|.*\{(.*)\}.*|\1|g' > doi.txt

重复上述命令直至最后一个老师的文献也搞定。

现在可以用 sci-hub 下载论文了,但是上 sci-hub 一个个下载 pdf 文件也麻烦得很,也用脚本吧,这个叫 sci-hub.sh 的脚本以后也用得着。

#!/bin/bash
shDownload() {
    # You may find working domains from Alexandra Elbakyan's facebook. I use /r/scihub
    prefix="http://sci-hub.hk"
    doi="$1"
    url="$(curl -sS --compressed "$prefix/$doi" | grep '?download=true' \
        | awk -F"'" '{print $2}')"
    if [[ -n "$url" ]]; then
        if [[ -n "$2" ]]; then
            filename="$2"
        else
            filename="$(basename "$doi").pdf"
        fi
        wget -O "$filename" "$url"
    else
        echo "$doi" >> notFoundOnSciHub.txt
    fi
}
shDownload "$@"

现在用刚刚保存的 doi 下载 pdf 文件吧。cat batchDownloader.sh

#!/bin/bash
for folder in */; do
    cd $folder
    len=$(wc < doi.txt | awk '{print $1}')
    for i in $(seq 1 $len); do
        # should have used bash array
        doi="$(sed -ne "${i}p" doi.txt | grep doi \
            | perl -pe  's|^.*\.org/||g')"
        echo "$doi"
        [[ -n "$doi" ]] && sci-hub.sh "$doi" "$i $(basename "$doi").pdf"
    done
    cd ..
done

把刚刚下载的文件重命名一下,方便老师使用。cat batchRenamer.sh

#!/bin/bash
for folder in */; do
    cd $folder
    for i in *.pdf; do
        doi="$(perl -pe 's|^[0-9]{1,}\s*||g; s|\.pdf$||g' <<< "${i}")"
        if grep "$doi$" titles.txt; then
            ln=$(nl < titles.txt | grep -E "$doi$" | awk {'print $1'})
            if [[ -n $ln ]]; then
                fn=$(sed -ne "$(($ln-1))p" titles.txt)
                mv "$i" "$(($ln/2)) $fn.pdf"
            fi
        fi
    done
    cd ..
done

啊,论文引用数,要去除所有作者的引用数。这个 citations.sh所有的引用数|他引数 保存到 citations.txt。这里不用使用 parallel, & 之类的招式,不然

access denied

#!/bin/bash
[[ -n "$1" ]] && id="$1" || exit
[[ -n "$2" ]] && file="$2" || exit
result="$(curl -L -sS "https://mathscinet.ams.org/mathscinet-getitem?mr=MR$id")"
citations="$(perl -ne 'print $1 if /From References:\s*([0-9]{1,})/' <<< "$result")"
othersCitations=$citations
authorids="$(grep authid <<< "$result" | grep -v 'Reviewed by' | perl -ne 'print "$1 \n" if m|(mrauthid=[0-9]{1,})">(.*?)</a>|')"
authors="$(grep authid <<< "$result" | grep -v 'Reviewed by' | perl -ne 'print " $2 \n" if m|(authid=[0-9]{1,})">(.*?)</a>|')"
printf "URL: http://www.ams.org/mathscinet-getitem?mr=MR$id\nAuthors: $(tr '\n' '|' <<< "$authors")\nTotal citations: $citations\n"
if [[ "$citations" -eq 0 ]]; then
    echo "$citations|$othersCitations" | tee -a $file
else
    citepage="$(curl -L -sS "https://mathscinet.ams.org/mathscinet/search/publications.html?loc=refcit&refcit=$id&r=1&extend=1")"
    list="$(grep authid <<< "$citepage" | grep -i -vE "checkbox|reviewed by")"
    if [[ "$citations" -eq "1" ]]; then
        while read authorid; do
            echo "grepping ${authorid:9}"
            if grep -i "$authorid" <<< "$list"; then
                othersCitations=0
                break
            fi
        done < <(echo "$authorids")
        echo "$citations|$othersCitations" | tee -a $file
    elif [[ "$citations" -eq "$(wc <<< "$list" | awk '{print $1}')" ]]; then
        while read authorid; do
            echo "grepping ${authorid:9}"
            [[ -n "$authorid" ]] && list=$(grep -i -v "$authorid" <<< "$list")
        done < <(echo "$authorids")
        if [[ -z "$list" ]]; then
            echo "$citations|0" | tee -a $file
        else
            othersCitations=$(wc <<< "$list" | awk '{print $1}')
            echo "$citations|$othersCitations" | tee -a $file
        fi
    else
        echo "error to get citations" | tee -a $file
    fi
fi

上面的 bash 脚本真他妈长,而且还不能把活干了。引用数高于 100 的需要改一下,我不管了。超过 10 行就不要用 bash 脚本了,此言得之,我在错误的道路上走得太远了,不想回头了。

现在要查询期刊的影响因子。我校没有订 Journal Citation Reports,所以我不知道 Thomson Reuters 的影响因子查询界面长啥样。我把这个网站上的影响因子信息全部下载下来。

result="$(curl http://www.scijournal.org/math-journal-impact-factor-list.shtml)"
prefix='http://www.scijournal.org/'
journals=$(grep impact-factor-of <<< "$result" \
    | perl -ne 'print "$1,$2\n" if m|href="(.*?)">(.*)</a>|g')
while read journal; do
    address="$(echo "$journal" | awk -F',' '{print $1}')"
    name="$(perl -pe 's|^(.*?),\s*||g' <<< "$journal")"
    echo "$name"
    ifp="$(curl -L -sS "$prefix/$address")"
    grep -E '2017.*<br>' <<< "$ifp" | perl -pe 's|<br>|\n|g' \
        | tee "$name"
done < <(echo "$journals")

把所有期刊的缩写放入 ../sName

cat */bib.bib | grep -i '^\s*journal\s*=' \
    | sort | uniq | perl -pe "s|\'E|&Eacute;|g" \
    | perl -pe 's|.*\{(.*)\}.*|\1|g' >> ../sName

然后一个个找期刊的影响因子(很多完整名称对不上号),为了使这个痛苦的过程短一点,我用 fzf 模糊搜索,然后自动复制影响因子,手动粘贴在对应期刊的下一行。

while true; do
    f="$(fzf)
    cat "$f"
    head -n1  < "$f" | xclip -selection clipboard; sleep 5
done

现在所有数据都有了,还要把引用数和影响因子插入文献,生成需要的格式。我首先用 bibtex2html 把 bib.bib 生成 apa 格式的 bibliography.

for i in *; do
    bibtex2html -nokeys -nofooter -unicode -nodoc -q \
        -o - -s apa $i/bib.bib > $i/bib.html
done

再插入影响因子。

insertImpactFactor() {
    file="$1"
    journals="$(awk 'NR % 2 != 0' ~/Sync/temp/journalsIP.txt)"
    ifs="$(awk 'NR % 2 == 0' ~/Sync/temp/journalsIP.txt)"
    for i in $(seq 1 $(wc <<< "$ifs" | awk '{print $1}')); do
        # should have used python, this is awkward and ugly
        journal="<em>$(sed -ne "${i}p" <<< "$journals")</em>"
        impactFactor="$(sed -ne "${i}p" <<< "$ifs" | perl -pe 's|.*:\s*||g')"
        if [[ -n "$impactFactor" ]]; then
            desc="$journal (impact factor: $impactFactor)"
            echo "$journal, $impactFactor, $desc"
            # sed -i -e "s|$journal|$desc|g" $file
            perl -i -pe "s#\Q$journal#$desc#g" $file
        fi
    done
}
for i in */bib.html; do
    insertImpactFactor $i
done

再插入引用数。

#!/bin/bash
insertCitations() {
    folder=$1
    list="$(nl -ba < $folder/bib.html | grep http | grep -v filliatr | tac -)"
    citations="$(awk -F'|' '{print $2}' $folder/citations.txt | tac -)"
    entries="$(wc <<< "$citations"|awk '{print $1}')"
    mr="$(tac $folder/mrURL.txt)"
    if [[ ! "$entries" -eq "$(wc <<< "$list"|awk '{print $1}')" ]]; then
        echo "numbers mismatch: $folder" | tee -a ~/mismatch
    else
        for i in $(seq 1 $entries); do
            line="$(sed -ne "${i}p" <<< "$list" | awk '{print $1}')"
            cited="$(sed -ne "${i}p" <<< "$citations")"
            mrurl="$(sed -ne "${i}p" <<< "$mr")"
            sed -i -E "${line}aCitations on <a href=\"$mrurl\">MathSciNet</a>: $cited" $folder/bib.html
        done
    fi
}
for i in *; do
    insertCitation $i
done

好 html 文件已经生成好了,老师可能不好编辑。用 pandoc 转成 docx 各 pdf 吧。

for i in *; do
    pandoc $i/bib.html -o $i/bib.docx
done
for i in *; do
    for file in $i/*.docx; do
        pandoc -F pandoc-citeproc -V mainfont='Times New Roman' \
            --pdf-engine=xelatex --metadata link-citations \
            $file -o "${file:0:-5}.pdf"
    done
done

好了,上面是理论上你需要的脚本。但是理想状况并不存在,事实上,我花了很多时间处理 outliners,事实上有一些论文没有被 review,有些论文找不到 bibtex,有很多 bibtex 没有 doi,有很多 doi 从 sci-hub 上下不到 pdf,有一些论文引用超过了 100,必须肉眼查看(不是必须肉眼看,这不是重复任务,不改了)。有些期刊影响因子不好找。总之,一个 data hoarder 诞生了。

我写了一堆大约再也没有什么鸟用的脚本。或许,sci-hub.shmref.py 是例外。因为你现在可以用它们来一秒钟下载一篇论文了。我这篇文章里的脚本可能不会更新了,我上面链接的文件可能会更新。遗憾的是,pdf 文件的换行是手动加个换行符,可能没有什么好的办法从 pdf 文件上复制出的一大串参考文献分离出一个个参考文献,然后一个个下载。

Publié le par v dans «misc».