Axivから文献一覧を被引用数を追加して取得

# 概要

- [arXiv](https://arxiv.org/)から文献一覧を被引用数を追加して取得する方法を忘備録として記載する。取得にはPythonを使用
- arXivのサイトだけでは引用数を確認できなかったため、Schemantic Scholarを活用する
- なお、本内容は以下のサイトを主に参考にした。ただし、パッケージ等の更新により修正が必要であったために投稿する
  - [ArxivのDeep Learning関連論文を被引用数順に1000本並べてみる](https://qiita.com/tmhssk1/items/9acca62c7dd3140f731d)
- 使用パッケージ（Python）
  - arxiv  (arXiv APIのPythonラッパー)
  - requests
  - numpy
  - pandas
  - openpyxl



# 文献の抽出

## arXivからの文献抽出
- Pythonのarxivのパッケージを用いて文献を抽出する
- Query（検索条件）の作成方法は以下を参照
  - [5.1. Details of Query Construction](https://info.arxiv.org/help/api/user-manual.html#51-details-of-query-construction)
  - ただし、上記ではスペースには"+"を使用する記載があるが、arxivのパッケージでは"+"を用いると想定した検索ができず
  - "+"ではなく普通にスペースを使用することで解決
- 例として、以下の条件で検索したプログラムを示す
  - 検索条件：要約に"RAG"を含む。カテゴリは"AI"。期間は2021/1/1から2025/5/4

<br>

- (補足) QueryのPrefix一覧

  | prefix |       explanation        |
  | :----: | :----------------------: |
  |   ti   |          Title           |
  |   au   |          Author          |
  |  abs   |         Abstract         |
  |   co   |         Comment          |
  |   jr   |    Journal Reference     |
  |  cat   |     Subject Category     |
  |   rn   |      Report Number       |
  |   id   | Id (use id_list instead) |
  |  all   |     All of the above     |




### プログラム例

- arxivから一覧の取得するプログラムの例を示す
- resultsはジェネレータである点に注意する。pandasのDataFrameへの変換例は後述する

```python
import arxiv


def set_client_search(query: str):
    client = arxiv.Client(
        page_size = 1000,   # 1リクエストでの抽出数。2000以下, default 100
        delay_seconds = 3.0,  # 遅延時間（秒）。rxivの規約上、1リクエスト以上/3秒はNG
    )

    search = arxiv.Search(
        query = query,  # 検索文字, str
        # id_list = [],   # 文献ID. List[str]
        max_results = 2000,  # 最大抽出数. 30000以下
        sort_by = arxiv.SortCriterion.Relevance,   # Relevance, LastUpdatedDate, SubmittedDate
        sort_order = arxiv.SortOrder.Descending,   # Ascending, Descending
    )
    return client, search

# 要約にRAGを含み、カテゴリ: AI、期間; 20210101-20250504で検索
query = "abs:RAG AND cat:cs.AI AND submittedDate:[202101010000 TO 202505042359]"  
client, search = set_client_search(query)
results = client.results(search)

```

## DataFrame型に変換
- 取得したresultsをDataFrame型に変換する
- 結果項目の詳細は[arXiv API User Manual: Details of Atom Results ReturnedarXiv](https://info.arxiv.org/help/api/user-manual.html#_details_of_atom_results_returned)を参照のこと
- 検索の関連度に関するパラメータが結果に含まれないため、"Relevance"として一致度を設定
- 一致度(Relevance)は、arxivでの検索結果をRelevanceでDescendingとしてソートしていることを利用して、0-1となるようにした

  

### プログラム例

```python
import pandas as pd

def convert_to_str(vals):  # 文字列に変換
    if isinstance(vals, list):
        return list(map(str, vals))
    elif vals is None:
        return vals
    else:
        return str(vals)
        
def create_df(results):
    res0 = []
    for res in results:
        res_dict = dict(
            entry_id = res.entry_id,
            pdf_url = res.pdf_url,
            updated = res.updated, 
            published = res.published, 
            title = res.title, 
            authors = convert_to_str(res.authors),  # 文字列に変換
            summary = res.summary,
            comment = res.comment,
            journal_ref = res.journal_ref,
            doi = res.doi,
            primary_category = res.primary_category,
            categories = res.categories,
            links = convert_to_str(res.links),  # 文字列に変換
        )
        res0.append(res_dict)
    df = pd.DataFrame(res0)
    ndata = len(df)
    df['Relevance'] = [i/ndata for i in range(ndata, 0, -1)]  # 一致度を0~1の範囲で設定。検索がRelevanceでDescendingでソートしているため。
    return df

df = create_df(results)

```



## Semantic Scholarによる引用数の取得
- arxivには引用数に関連する項目がないため、Semantic ScholarのAPIを利用
- Shemantic ScholarのAPIについては以下を参照のこと
  - [Semantic Scholar API Tutorial](https://www.semanticscholar.org/product/api%2Ftutorial)
  - [Get details for multiple papers at once](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/post_graph_get_papers)

- 追加する項目は以下とした
  - citationCount: 被引用数
  - influentialCitationCount: 影響力があると判断された引用数
  - referenceCount: 参考文献数（参考用）

    



### プログラム例
- バッチ処理するための文献IDを"arxiv_id"として設定する
- バッチ処理において、サーバ状況・リクエストの間隔によってエラーが生じやすい。再実行時に不都合が生じにくいようにデータ(DataFame)は結合(merge)ではなく、updateを使用
  - 一度に処理できる文献ID数は500件である点に注意する

    

```python
import numpy as np

def create_url(aid: str):
    apath = aid.split("/")[-1].split("v")[0]
    arxiv_id = f'ARXIV:{apath}'
    search_url = f'{semantic_base_url}/{arxiv_id}' + '?fields=' + fields 
    return arxiv_id

semantic_base_url = 'https://api.semanticscholar.org/graph/v1/paper'  # Semantic ScholarのベースURL
fields = 'citationCount,influentialCitationCount,referenceCount'  # サイトから取得する追加項
df['arxiv_id'] = df['entry_id'].apply(create_url)
tcol = fields.split(",") 
df[tcol] = np.nan  # 初期化

```




- バッチ処理

```python
import requests
import time

def get_citation_batch(df, start=0, batch_size=500):
    ndata = len(df)
    nbatch = (ndata // batch_size) + 1
    data = df['arxiv_id'].tolist()
    for i in range(start, ndata, batch_size):
        batch = data[i:i+batch_size]  
        semantic_batch_url = semantic_base_url + '/batch'
        # ---
        print(f"{i+1}/{nbatch}  Processing batch: {i} to {i+batch_size}")
        r = requests.post(semantic_batch_url, params={'fields': fields}, json={"ids": batch})
        print(r.status_code)
        if r.status_code != 200:
            print('---')
            print('Error: ', r.status_code)
            if r.status_code == 429:
                print('Too many requests. Wait a while and try again.')
            return df
        # ---
        batch_output = dict(zip(batch, r.json()))
        batch_output = pd.DataFrame(batch_output).T
        df.set_index('arxiv_id', inplace=True)  # arxiv_idをキーとしてdfのデータを更新
        df.update(batch_output)
        df.reset_index(inplace=True)
        # ---
        time.sleep(5)  # 時間を空けるため
    return df


start = 0  # 開始行
batch_size = 500  # バッチ数。500以下
df = get_citation_batch(df, start=start, batch_size=batch_size)

```



## データの整理(ソート、フィルタ)と出力
- データをソートしてエクセル出力する
  - 本例ではinfluentialCitaionCount, citationCount, Relevanceの順に降順でソート
- クリックして参照PDFに飛べるようにハイパーリンクを追加
  - リンク追加前: arxiv_list.xlsx として出力。csv出力しても良い
  - リンク追加後: add_link_arxiv_list.xlsx として出力

    


```python
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font

outfilename = "arxiv_list.xlsx"
data = df.copy()

# データのソート
tcols = fields.split(",") + ['Relevance']
data[tcols] = data[tcols].apply(pd.to_numeric, errors='coerce')  # ignore, coerce
tcols = ['updated', 'published']
for t in tcols:
    data[t] = data[t].apply(lambda x: x.strftime('%Y-%m-%d'))
data = data.sort_values(by=['influentialCitationCount', 'citationCount', 'Relevance'], ascending=[False, False, False])
data.to_excel(outfilename, index=False)

# ハイパーリンクの追加
wb = load_workbook(outfilename)
ws = wb.active

hyperlink_colname = ['entry_id', 'pdf_url']
col_index = [data.columns.get_loc(s) for s in hyperlink_colname]
for row in range(2, ws.max_row + 1):  # Excelの行は1始まり
    for c in col_index:
        cell = ws[f"{get_column_letter(c+1)}{row}"]  # B列（2列目）
        cell.value = f'=HYPERLINK("{cell.value}", "{cell.value}")'  # Excelのハイパーリンク関数
        cell.font = Font(color="0563C1", underline="single")  # デフォルトの青色（#0563C1）+ アンダーライン
wb.save('add_link_' + outfilename)
wb.close()

```



# 参照サイト
- PyPi: arxiv
  - [arxiv - PyPi](https://pypi.org/project/arxiv/)
  - [arxiv python doc](https://lukasschwab.me/arxiv.py/arxiv.html)
- arXiv API
  - [arXiv API User Manual](https://info.arxiv.org/help/api/user-manual.html#query_details)
  - [5.1. Details of Query Construction](https://info.arxiv.org/help/api/user-manual.html#51-details-of-query-construction)
- Qiita
  - [ArxivのDeep Learning関連論文を被引用数順に1000本並べてみる](https://qiita.com/tmhssk1/items/9acca62c7dd3140f731d)
- Semantic Scholar API
  - [Semantic Scholar API Tutorial](https://www.semanticscholar.org/product/api%2Ftutorial)
  - [Get details for multiple papers at once](https://api.semanticscholar.org/api-docs/#tag/Paper-Data/operation/post_graph_get_papers)