用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/brycewang-stanford/Auto-Empirical-Research-Skills --skill academic-web-scraping命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
Route empirical-research requests through the Auto-Empirical Research Skills catalog when this whole repository is installed as one skill in Codex, CodeBuddy, Claude Code, or another IDE. Use to choose and load the right vendored AERS skill for causal inference, econometrics, replication, data acquisition, manuscript writing, peer review and referee responses, citation checking, de-AIGC editing, or full empirical-paper workflows without reading the entire repository at once.
中英双语学术降 AIGC / bilingual academic de-AIGC skill. Removes AI-generated writing signatures from empirical papers in economics, management, and the social sciences — in both English and Chinese. Covers Turnitin AI, GPTZero, Originality.ai on the English side and 知网 AMLC, 万方, 维普 on the Chinese side. Uses a six-step loop (intake → audit → claim-evidence check → differentiated rewrite → five-dimension self-score → cold-reader recheck) with two pattern libraries (22 English + 17 Chinese patterns), section-by-section strategies for empirical papers, and hard protections that keep every number, coefficient, and citation intact.
Use when a research task needs reproducible Kaggle discovery, metadata inspection, bounded public-data downloads, competition or kernel discovery, model discovery, or an explicitly approved Kaggle write/delete operation through the official CLI.
基于 SOC 职业分类
正在显示 SKILL.md
| name | academic-web-scraping |
| description | Ethical web scraping and API-based data collection for research |
| metadata | {"openclaw":{"emoji":"🌐","category":"tools","subcategory":"scraping","keywords":["web scraping","API data collection","web search strategies","data extraction"],"source":"N/A"}} |
Research often requires collecting data from the web -- whether it is bibliographic metadata from academic databases, experimental datasets from public repositories, social media posts for computational social science, or economic indicators from government portals. Web scraping and API-based data collection are essential skills for modern researchers across disciplines.
This guide covers both approaches: structured API access for platforms that provide one, and web scraping for when no API exists. It emphasizes ethical data collection practices, including respecting robots.txt, rate limiting, terms of service compliance, and IRB considerations for human-subject data. The goal is to collect research data reliably and responsibly.
Whether you are building a dataset for a machine learning paper, collecting metadata for a systematic review, or gathering public data for policy research, these patterns help you do it correctly and efficiently.
APIs are always preferable to scraping when available. They provide structured data, are officially supported, and have clear usage terms.
| API | Data | Rate Limit | Auth |
|---|---|---|---|
| OpenAlex | Papers, authors, venues, concepts | 100K req/day | Email in header |
| Crossref | DOI metadata | 50 req/sec (polite pool) | Email in header |
| PubMed (Entrez) | Biomedical literature | 10 req/sec (with key) | API key (free) |
| arXiv | Preprints | 1 req/3sec | None |
| CORE | Open access papers | 10 req/sec | API key (free) |
import requests
import time
class OpenAlexClient:
BASE_URL = "https://api.openalex.org"
def __init__(self, email):
self.session = requests.Session()
self.session.headers.update({
'User-Agent': f'ResearchBot/1.0 (mailto:{email})'
})
def search_works(self, query, filters=None, per_page=25, max_results=100):
"""Search for works with optional filters."""
results = []
page = 1
while len(results) < max_results:
params = {
'search': query,
'per_page': min(per_page, max_results - len(results)),
'page': page,
}
if filters:
params['filter'] = ','.join(f'{k}:{v}' for k, v in filters.items())
resp = self.session.get(f'{self.BASE_URL}/works', params=params)
resp.raise_for_status()
data = resp.json()
works = data.get('results', [])
if not works:
break
results.extend(works)
page += 1
time.sleep()
results[:max_results]
():
resp = .session.get()
resp.raise_for_status()
resp.json()
client = OpenAlexClient(email=)
papers = client.search_works(
,
filters={
: ,
: ,
:
},
max_results=
)
paper papers[:]:
()
()
()
from Bio import Entrez
Entrez.email = "researcher@university.edu"
Entrez.api_key = os.environ.get("NCBI_API_KEY") # optional
def search_pubmed(query, max_results=100):
"""Search PubMed and retrieve article details."""
# Search
handle = Entrez.esearch(db="pubmed", term=query,
retmax=max_results, sort="relevance")
search_results = Entrez.read(handle)
id_list = search_results["IdList"]
if not id_list:
return []
# Fetch details
handle = Entrez.efetch(db="pubmed", id=id_list,
rettype="xml", retmode="xml")
records = Entrez.read(handle)
articles = []
for article in records['PubmedArticle']:
medline = article['MedlineCitation']
art_info = medline['Article']
articles.append({
'pmid': str(medline['PMID']),
'title': art_info.get('ArticleTitle', ''),
'abstract': art_info.get('Abstract', {}).get(
'AbstractText', [''])[0] if 'Abstract' in art_info else '',
'journal': art_info[][],
: art_info[][].get(
, {}).get(, ),
})
articles
When no API exists, scraping becomes necessary. Always check for an API first.
| Tool | Type | JavaScript Support | Speed | Learning Curve |
|---|---|---|---|---|
| requests + BeautifulSoup | HTTP + parsing | No | Fast | Low |
| Scrapy | Framework | No (without middleware) | Very fast | Medium |
| Selenium | Browser automation | Yes | Slow | Medium |
| Playwright | Browser automation | Yes | Medium | Medium |
| httpx | Async HTTP | No | Very fast | Low |
import requests
from bs4 import BeautifulSoup
import time
def scrape_conference_proceedings(url, delay=2.0):
"""Scrape paper titles and links from a conference page."""
headers = {
'User-Agent': 'ResearchBot/1.0 (Academic research; contact@university.edu)'
}
response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'html.parser')
papers = []
for item in soup.select('.paper-item, .proceeding-entry'):
title_el = item.select_one('.title, h3, h4')
link_el = item.select_one('a[href]')
authors_el = item.select_one('.authors, .author-list')
if title_el:
papers.append({
'title': title_el.get_text(strip=True),
'url': link_el['href'] if link_el else None,
'authors': authors_el.get_text(strip=True) if authors_el else '',
})
time.sleep(delay) # Respect the server
return papers
from playwright.sync_api import sync_playwright
def scrape_dynamic_page(url):
"""Scrape a JavaScript-rendered page using Playwright."""
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
page = browser.new_page()
page.goto(url, wait_until='networkidle')
# Wait for content to load
page.wait_for_selector('.results-container', timeout=10000)
# Extract data
items = page.query_selector_all('.result-item')
results = []
for item in items:
title = item.query_selector('.title')
results.append({
'title': title.inner_text() if title else '',
})
browser.close()
return results
https://example.com/robots.txt specifies what is allowed.from urllib.robotparser import RobotFileParser
def can_scrape(url, user_agent='*'):
"""Check if scraping a URL is allowed by robots.txt."""
from urllib.parse import urlparse
parsed = urlparse(url)
robots_url = f"{parsed.scheme}://{parsed.netloc}/robots.txt"
rp = RobotFileParser()
rp.set_url(robots_url)
rp.read()
allowed = rp.can_fetch(user_agent, url)
crawl_delay = rp.crawl_delay(user_agent)
return {
'allowed': allowed,
'crawl_delay': crawl_delay or 1.0,
}
import json
import csv
from pathlib import Path
from datetime import datetime
class DataCollector:
def __init__(self, output_dir='collected_data'):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
def save_json(self, data, filename):
path = self.output_dir / f'{filename}_{self.timestamp}.json'
with open(path, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
print(f"Saved {len(data)} records to {path}")
def save_csv(self, data, filename, fieldnames=None):
if not data:
return
if fieldnames is None:
fieldnames = list(data[0].keys())
path = .output_dir /
(path, , newline=, encoding=) f:
writer = csv.DictWriter(f, fieldnames=fieldnames,
extrasaction=)
writer.writeheader()
writer.writerows(data)
()
():
path = .output_dir /
(path, , encoding=) f:
json.dump({
: .timestamp,
: (data),
: data,
}, f, indent=, ensure_ascii=)