-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfetch_abs.py
More file actions
168 lines (132 loc) · 5.31 KB
/
Copy pathfetch_abs.py
File metadata and controls
168 lines (132 loc) · 5.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import time
import feedparser
import requests
from requests.adapters import HTTPAdapter, Retry
from typing import Dict, List, Optional
import json
from thefuzz import fuzz
from diskcache import Cache
cache = Cache('cache/semantic_scholar_references')
abs_cache = Cache('cache/abstract')
class ReferenceAbstractFetcher:
def __init__(self):
self.base_url = "https://api.semanticscholar.org/graph/v1/paper"
def get_paper_references_cached(self, title: str) -> List[Dict]:
references = cache.get(title, None)
if references is not None:
return references
references = self.get_paper_references(title)
if references is not None:
cache[title] = references
return references
def get_paper_references(self, title: str) -> Optional[List[Dict]]:
while True:
response = requests.get(
f'{self.base_url}/search/match',
params={
'query': title,
'fields': 'references.title,references.abstract,references.year,references.externalIds'
},
)
time.sleep(1)
if response.status_code == 200:
data = response.json()
if data.get('data') and len(data['data']) > 0:
return data['data'][0].get('references', None)
elif response.status_code == 429:
continue
else:
response.raise_for_status()
def match_reference(ref_title: str, bib_entries: List[Dict], threshold: int = 85) -> str:
best_match = None
best_score = 0
for entry in bib_entries:
if 'title' not in entry:
continue
titles = entry['title']
if isinstance(titles, str):
titles = [titles]
for title in titles:
score = fuzz.partial_ratio(ref_title.lower(), title.lower())
score_full = fuzz.ratio(ref_title.lower(), title.lower())
score = score + score_full / 128
if score >= threshold and score > best_score:
best_score = score
best_match = getattr(entry, 'key', None) or entry['ID']
return best_match
def get_arxiv_api(id_list: str):
session = requests.Session()
session.mount('https://export.arxiv.org', HTTPAdapter(max_retries=Retry(read=5)))
url = 'https://export.arxiv.org/api/query'
entries = []
num_max_results = len(entries) + 1
start = 0
num_items_per_page = 1000
while len(entries) < num_max_results:
param = dict(
id_list=id_list,
start=start,
max_results=num_items_per_page,
sortBy='submittedDate',
sortOrder='ascending',
)
result = session.post(url, data=param)
result.raise_for_status()
data = feedparser.parse(result.text)
num_max_results = int(data['feed']['opensearch_totalresults'])
entries += data['entries']
start += len(data['entries'])
time.sleep(3)
return entries
def update_abstract_cache(references: list[dict]) -> None:
arxiv_ids = {}
for ref in references:
if ref['paperId'] in abs_cache:
pass
elif 'abstract' in ref and ref['abstract'] is not None:
abs_cache[ref['paperId']] = ref['abstract']
elif 'ArXiv' in (ref['externalIds'] or {}):
arxiv_ids[ref['externalIds']['ArXiv']] = ref['paperId']
if len(arxiv_ids) == 0:
return
entries = get_arxiv_api(','.join(arxiv_ids.keys()))
for entry in entries:
arxiv_id: str = entry['id'].split('/')[-1].split('v')[0]
try:
abs_cache[arxiv_ids[arxiv_id]] = entry['summary']
except KeyError:
pass
def process_paper_references(title: str, bib_entries: List[Dict]) -> Dict[str, Dict]:
fetcher = ReferenceAbstractFetcher()
citations_map = {}
print(f"Fetching references for paper '{title}'...")
references = fetcher.get_paper_references_cached(title)
if not references:
print("No reference information found")
return {}
print(f"Found {len(references)} references")
print("Updating abstract cache...")
update_abstract_cache(references)
print("Matching bib entries...")
for ref in references:
ref['abstract'] = abs_cache.get(ref['paperId'], ref['abstract'])
for ref in references:
if not ref.get('title'):
print("[Warning] Semantic Scholar returned a reference without a title")
continue
if not ref.get('abstract'):
print(f"[Warning] Semantic Scholar returned a reference without an abstract: {ref['title']}")
continue
bib_id = match_reference(ref['title'], bib_entries)
if bib_id:
citations_map[bib_id] = {
'title': ref['title'],
'abstract': ref['abstract'],
}
print(f"Matched {len(citations_map)} references")
for k, v in citations_map.items():
print(k, v['title'])
return citations_map
def save_citations_map(citations_map: Dict[str, Dict], output_file: str):
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(citations_map, f, ensure_ascii=False, indent=2)