crawler.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #!/usr/bin/env python3
  2. """营养产品爬虫(试点)— 抓取京东/Amazon 商品数据,输出 JSON 中间文件。
  3. 用法:
  4. python crawler.py --platform jd --query "益生菌"
  5. python crawler.py --platform amazon --query "probiotic"
  6. python crawler.py --seed # 按 seed_products.json 抓取
  7. """
  8. import argparse
  9. import json
  10. import os
  11. import random
  12. import time
  13. from datetime import datetime
  14. import requests
  15. from bs4 import BeautifulSoup
  16. OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "output")
  17. HEADERS = {
  18. "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
  19. "(KHTML, like Gecko) Chrome/120.0 Safari/537.36",
  20. "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
  21. }
  22. def ensure_output_dir():
  23. os.makedirs(OUTPUT_DIR, exist_ok=True)
  24. def fetch(url, headers=None, timeout=15):
  25. """带重试和随机延迟的请求"""
  26. h = {**HEADERS, **(headers or {})}
  27. for attempt in range(3):
  28. try:
  29. resp = requests.get(url, headers=h, timeout=timeout)
  30. resp.raise_for_status()
  31. time.sleep(random.uniform(1.0, 2.5))
  32. return resp
  33. except requests.RequestException as e:
  34. print(f"[{attempt+1}/3] 请求失败 {url}: {e}")
  35. time.sleep(2 * (attempt + 1))
  36. return None
  37. def crawl_jd(query):
  38. """京东搜索抓取(返回商品名称/价格/链接)"""
  39. results = []
  40. url = f"https://search.jd.com/Search?keyword={query}&enc=utf-8"
  41. resp = fetch(url)
  42. if not resp:
  43. return results
  44. soup = BeautifulSoup(resp.text, "html.parser")
  45. items = soup.select(".gl-item")
  46. for item in items[:10]:
  47. try:
  48. name_el = item.select_one(".p-name em")
  49. price_el = item.select_one(".p-price strong i")
  50. link_el = item.select_one(".p-img a")
  51. name = name_el.get_text(strip=True) if name_el else None
  52. price_str = price_el.get_text(strip=True) if price_el else None
  53. link = "https:" + link_el["href"] if link_el and link_el.get("href") else None
  54. results.append({
  55. "platform": "京东",
  56. "query": query,
  57. "name": name,
  58. "price": float(price_str) if price_str else None,
  59. "url": link,
  60. "crawled_at": datetime.now().isoformat(),
  61. })
  62. except Exception as e:
  63. print(f"解析商品异常: {e}")
  64. return results
  65. def crawl_amazon(query):
  66. """Amazon 搜索(中文/英文)抓取"""
  67. results = []
  68. url = f"https://www.amazon.com/s?k={query}"
  69. resp = fetch(url, headers={"User-Agent": HEADERS["User-Agent"]})
  70. if not resp:
  71. return results
  72. soup = BeautifulSoup(resp.text, "html.parser")
  73. items = soup.select('[data-component-type="s-search-result"]')
  74. for item in items[:10]:
  75. try:
  76. name_el = item.select_one("h2 span")
  77. price_whole = item.select_one(".a-price-whole")
  78. link_el = item.select_one("h2 a")
  79. name = name_el.get_text(strip=True) if name_el else None
  80. price_str = price_whole.get_text(strip=True) if price_whole else None
  81. link = "https://www.amazon.com" + link_el["href"] if link_el and link_el.get("href") else None
  82. results.append({
  83. "platform": "Amazon",
  84. "query": query,
  85. "name": name,
  86. "price": float(price_str.replace(",", "")) if price_str else None,
  87. "url": link,
  88. "crawled_at": datetime.now().isoformat(),
  89. })
  90. except Exception as e:
  91. print(f"解析商品异常: {e}")
  92. return results
  93. def save(platform, data):
  94. ensure_output_dir()
  95. path = os.path.join(OUTPUT_DIR, f"{platform}.json")
  96. with open(path, "w", encoding="utf-8") as f:
  97. json.dump(data, f, ensure_ascii=False, indent=2)
  98. print(f"已保存 {len(data)} 条到 {path}")
  99. def main():
  100. parser = argparse.ArgumentParser(description="营养产品爬虫")
  101. parser.add_argument("--platform", choices=["jd", "amazon"], help="平台")
  102. parser.add_argument("--query", help="搜索关键词")
  103. parser.add_argument("--seed", action="store_true", help="按种子清单抓取")
  104. args = parser.parse_args()
  105. if args.seed:
  106. seed_path = os.path.join(os.path.dirname(__file__), "seed_products.json")
  107. with open(seed_path, encoding="utf-8") as f:
  108. seeds = json.load(f)
  109. all_jd, all_amazon = [], []
  110. for seed in seeds:
  111. print(f"抓取: {seed['name']}")
  112. all_jd.extend(crawl_jd(seed["keywords"]))
  113. all_amazon.extend(crawl_amazon(seed["keywords"]))
  114. time.sleep(random.uniform(2, 4))
  115. save("jd", all_jd)
  116. save("amazon", all_amazon)
  117. elif args.platform and args.query:
  118. if args.platform == "jd":
  119. save("jd", crawl_jd(args.query))
  120. elif args.platform == "amazon":
  121. save("amazon", crawl_amazon(args.query))
  122. else:
  123. parser.print_help()
  124. if __name__ == "__main__":
  125. main()