# ArXiv — Scraping & Data Extraction `https://arxiv.org` — open-access preprint server. **Never use the browser for ArXiv.** All data is reachable via `http_get` using the Atom API or HTML meta tags. No API key required. ## Do this first **Use the Atom API for any paper search or metadata fetch — one call, XML response, no auth.** ```python import xml.etree.ElementTree as ET from helpers import http_get NS = {'atom': 'http://www.w3.org/2005/Atom', 'arxiv': 'http://arxiv.org/schemas/atom'} xml = http_get("http://export.arxiv.org/api/query?search_query=ti:transformer+AND+cat:cs.LG&max_results=5&sortBy=submittedDate&sortOrder=descending") root = ET.fromstring(xml) entries = root.findall('atom:entry', NS) ``` Use `id_list` for known paper IDs — supports comma-separated batch fetch in a single call. Use `http_get` on `https://arxiv.org/abs/{id}` + regex for `citation_*` meta tags when you need the full abstract from an HTML page. ## Common workflows ### Search papers (API) ```python import xml.etree.ElementTree as ET from helpers import http_get NS = {'atom': 'http://www.w3.org/2005/Atom', 'arxiv': 'http://arxiv.org/schemas/atom'} xml = http_get( "http://export.arxiv.org/api/query" "?search_query=ti:transformer+AND+cat:cs.LG" "&max_results=5&sortBy=submittedDate&sortOrder=descending" ) root = ET.fromstring(xml) entries = root.findall('atom:entry', NS) for e in entries: title = e.find('atom:title', NS).text.strip().replace('\n', ' ') arxiv_id = e.find('atom:id', NS).text.split('/')[-1] # e.g. '2604.15259v1' published = e.find('atom:published', NS).text[:10] # '2026-04-16' updated = e.find('atom:updated', NS).text[:10] abstract = e.find('atom:summary', NS).text.strip() authors = [a.find('atom:name', NS).text for a in e.findall('atom:author', NS)] cats = [c.get('term') for c in e.findall('atom:category', NS)] primary = e.find('arxiv:primary_category', NS).get('term') comment = e.find('arxiv:comment', NS) pdf_link = next((l.get('href') for l in e.findall('atom:link', NS) if l.get('title') == 'pdf'), None) abs_link = next((l.get('href') for l in e.findall('atom:link', NS) if l.get('rel') == 'alternate'), None) print(arxiv_id, published, title[:60]) print(" Authors:", authors[:2]) print(" PDF:", pdf_link) # Confirmed output (2026-04-18): # 2604.15259v1 2026-04-16 Stability and Generalization in Looped Transformers # Authors: ['Asher Labovich'] # PDF: https://arxiv.org/pdf/2604.15259v1 ``` ### Fetch single paper by ID (API) ```python import xml.etree.ElementTree as ET from helpers import http_get NS = {'atom': 'http://www.w3.org/2005/Atom', 'arxiv': 'http://arxiv.org/schemas/atom'} xml = http_get("http://export.arxiv.org/api/query?id_list=1706.03762") root = ET.fromstring(xml) e = root.find('atom:entry', NS) title = e.find('atom:title', NS).text.strip() abstract = e.find('atom:summary', NS).text.strip() categories = [c.get('term') for c in e.findall('atom:category', NS)] pdf_link = next((l.get('href') for l in e.findall('atom:link', NS) if l.get('title') == 'pdf'), None) print("Title:", title) print("Categories:", categories) print("PDF:", pdf_link) print("Abstract:", abstract[:200]) # Confirmed output: # Title: Attention Is All You Need # Categories: ['cs.CL', 'cs.LG'] # PDF: https://arxiv.org/pdf/1706.03762v7 # Abstract: The dominant sequence transduction models are based on complex recurrent... ``` ### Batch fetch by comma-separated IDs (single call — fast) Fetching 10 IDs in one call takes ~2s. Prefer this over parallel single-ID fetches. ```python import xml.etree.ElementTree as ET from helpers import http_get NS = {'atom': 'http://www.w3.org/2005/Atom'} ids = ['1706.03762', '1810.04805', '2005.14165'] # Transformer, BERT, GPT-3 xml = http_get(f"http://export.arxiv.org/api/query?id_list={','.join(ids)}&max_results={len(ids)}") root = ET.fromstring(xml) for e in root.findall('atom:entry', NS): arxiv_id = e.find('atom:id', NS).text.split('/')[-1] title = e.find('atom:title', NS).text.strip() published = e.find('atom:published', NS).text[:10] print(arxiv_id, published, title[:60]) # Confirmed output: # 1512.03385v1 2015-12-10 Deep Residual Learning for Image Recognition # 1706.03762v7 2017-06-12 Attention Is All You Need # 2005.14165v4 2020-05-28 Language Models are Few-Shot Learners # 1810.04805v2 2018-10-11 BERT: Pre-training of Deep Bidirectional Transformers... # Note: order returned may differ from order requested ``` ### Parallel fetch (ThreadPoolExecutor for independent IDs) Use only when IDs are not known upfront or when mixing with other work. For pure batch, single comma-separated `id_list` call is faster. ```python import xml.etree.ElementTree as ET from concurrent.futures import ThreadPoolExecutor from helpers import http_get NS = {'atom': 'http://www.w3.org/2005/Atom'} def fetch_paper(arxiv_id): xml = http_get(f"http://export.arxiv.org/api/query?id_list={arxiv_id}") root = ET.fromstring(xml) e = root.find('atom:entry', NS) if e is None: return None return { 'id': arxiv_id, 'title': e.find('atom:title', NS).text.strip(), 'published': e.find('atom:published', NS).text[:10], } ids = ['1706.03762', '1810.04805', '2005.14165'] with ThreadPoolExecutor(max_workers=3) as ex: papers = list(ex.map(fetch_paper, ids)) for p in papers: print(p['id'], p['published'], p['title'][:60]) # Confirmed working — max_workers=3 is safe; don't exceed 5 for continuous crawling ``` ### HTML abstract page — citation_* meta tags Use this when you want the full abstract or the versionless PDF URL without parsing Atom XML. ```python import re from helpers import http_get html = http_get("https://arxiv.org/abs/1706.03762", headers={"User-Agent": "Mozilla/5.0"}) # HTML page is ~48 KB, fully static, no JS required title = re.search(r' 0` before accessing `entries[0]`. - **`arxiv:comment` and `arxiv:journal_ref` / `arxiv:doi` may be absent.** Not all papers have these fields. Use `e.find('arxiv:comment', NS)` and check `if el is not None and el.text`. - **Rate limit: 3 seconds between requests recommended for bulk crawling.** In practice, rapid bursts of 10 individual requests complete in ~6s (avg 0.63s/req) without being blocked. For sustained crawls over hundreds of papers, insert `time.sleep(3)` between requests. The API does not return rate limit headers — it just starts slowing responses or returns HTTP 503 silently. - **`citation_author` tags are in `"Last, First"` format**, not `"First Last"` like the Atom API. The Atom `atom:author/atom:name` field gives `"First Last"` order. Pick the format that matches your downstream use. - **The `arxiv:affiliation` sub-element of `atom:author` is rarely populated.** Most institutional affiliations are absent from the API response even when listed on the paper. The HTML abs page doesn't expose them in meta tags either. - **`sortBy=relevance` applies only with `search_query`.** Using `sortBy=relevance` with `id_list` has no effect — results still come back in date order. - **`max_results` cap is 2000 per call.** For bulk harvesting of a category, use `start` offset pagination and add 3s sleep between pages. `opensearch:totalResults` tells you the total so you can compute how many pages are needed. - **HTML `citation_abstract` meta tag contains the full abstract.** Unlike the Atom `atom:summary` which can have trailing whitespace and embedded newlines, the meta tag version is a single clean string — no `.strip()` needed.