记一次波折的 cursor 天生代码抓取《数据库内核月报》

[复制链接]
发表于 2025-8-9 03:40:39 | 显示全部楼层 |阅读模式
       今晚跟cursor斗智斗勇一个小时,终于成功实现抓取《数据库内核月报》的需求,比起csdn过程坎坷不少,知识点也更多,也更充分体现了提示词、需求拆解和分析的紧张性。
        过程太长,先简单纪录聊天过程,后续有时间再细化整理

一、 初始需求

你是一个Python爬虫专家,你需要天生一段Python代码,获取 http://mysql.taobao.org/monthly/ 中的所有文章链接,并将文档内容以markdown格式存放在当地taobao目次中。
其他留意事项:
1. 分辨页面渲染方式,避免链接抓取错误
2. 为提拔获取服从,你应该思量利用多线程及其他技术提效
3. 避免被反爬技术拦截

推荐代码如下:
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. BASE_URL = "http://mysql.taobao.org/monthly/"
  9. HEADERS = {
  10.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  11. }
  12. SAVE_DIR = "taobao"
  13. MAX_RETRIES = 3
  14. THREADS = 8
  15. os.makedirs(SAVE_DIR, exist_ok=True)
  16. def get_with_retry(url, retries=MAX_RETRIES, **kwargs):
  17.     for i in range(retries):
  18.         try:
  19.             resp = requests.get(url, headers=HEADERS, timeout=10, **kwargs)
  20.             if resp.status_code == 200:
  21.                 return resp
  22.         except Exception:
  23.             if i == retries - 1:
  24.                 raise
  25.             time.sleep(random.uniform(1, 3))
  26.     return None
  27. def get_all_links():
  28.     resp = get_with_retry(BASE_URL)
  29.     soup = BeautifulSoup(resp.text, "lxml")
  30.     links = []
  31.     for a in soup.select("a"):
  32.         href = a.get("href", "")
  33.         # 文章链接通常形如 202x-xx.html
  34.         if re.match(r"\d{4}-\d{2}\.html", href):
  35.             links.append(BASE_URL + href)
  36.     return links
  37. def save_article(url):
  38.     try:
  39.         resp = get_with_retry(url)
  40.         soup = BeautifulSoup(resp.text, "lxml")
  41.         # 获取文章标题
  42.         title = soup.title.text.strip() if soup.title else url.split("/")[-1]
  43.         # 获取正文内容
  44.         content_div = soup.find("div", class_="content")
  45.         if not content_div:
  46.             content_div = soup.find("div", id="content")
  47.         content = content_div.get_text("\n", strip=True) if content_div else soup.get_text("\n", strip=True)
  48.         # 生成markdown内容
  49.         md_content = f"# {title}\n\n原文链接: [{url}]({url})\n\n---\n\n{content}"
  50.         # 文件名
  51.         filename = os.path.join(SAVE_DIR, url.split("/")[-1].replace(".html", ".md"))
  52.         with open(filename, "w", encoding="utf-8") as f:
  53.             f.write(md_content)
  54.         print(f"已保存: {filename}")
  55.         # 随机延时,防止被封
  56.         time.sleep(random.uniform(0.5, 1.5))
  57.     except Exception as e:
  58.         print(f"下载失败: {url},原因: {e}")
  59. def main():
  60.     links = get_all_links()
  61.     print(f"共发现{len(links)}篇文章,开始下载...")
  62.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  63.         futures = [executor.submit(save_article, url) for url in links]
  64.         for future in as_completed(futures):
  65.             pass
  66.     print("全部下载完成。")
  67. if __name__ == "__main__":
  68.     main()
复制代码
二、 共发现0篇文章

文章获取失败,提示“共发现0篇文章,开始下载”,分析原因并优化代码实现
标题分析:

  • 你的爬虫代码之以是“共发现0篇文章”,很大概是因为页面源码中的文章链接格式与你的正则表达式不符,或者页面内容被重定向、反爬等原因影响,导致没有正确抓取到链接。
优化发起:

  • 调试页面源码:直接打印页面源码的前几百字符,确认文章链接的真实格式。
  • 放宽正则匹配:有些链接大概不是 202x-xx.html,而是 202x-xx/ 或其他格式。
  • 检查编码和反爬:有些网站会根据 User-Agent 返回差别内容,或返回乱码。
调试代码片段(发起先运行,观察输出):
  1. import requests
  2. url = "http://mysql.taobao.org/monthly/"
  3. headers = {
  4.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  5. }
  6. resp = requests.get(url, headers=headers)
  7. print(resp.status_code)
  8. print(resp.apparent_encoding)
  9. print(resp.text[:1000])  # 打印前1000字符
复制代码
请运行上面代码,贴出输出内容,这样我可以帮你精准调整正则和解析逻辑,确保能抓到所有文章链接。
如果你希望我直接给出更鲁棒的爬虫代码,也可以告知你希望的策略(比如:更宽松的正则、直接抓取所有以 .html 结尾的链接等)。
结果如下

D:\book_download\venv\Scripts\python.exe D:/book_download/book_download/book/ai_get_url.py
200
utf-8
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

<!-- <title>数据库内核月报</title> -->
<meta name="description" content="数据库内核月报, 来着阿里云 PolarDB 数据库内核团队。">

<link rel="stylesheet" href="/monthly/css/typo.css">
<link rel="stylesheet" href="/monthly/css/animate.css">
<link rel="stylesheet" href="/monthly/css/main.css">
<link rel="canonical" href="http://mysql.taobao.org//monthly/">
<link rel="alternate" type="application/rss+xml" title="数据库内核月报" href="http://mysql.taobao.org//monthly/feed.xml" />

<link rel="stylesheet" href="//cdn.staticfile.org/highlight.js/8.3/styles/tomorrow.min.css">
<script src="/monthly/js/highlight.min.js"></script>
<!-- <link rel="stylesheet" href="/monthly/themes/tomorrow.css">
<script src="/monthly/highlight/highlight.pack.js"> -->
<script>hl

Process finished with exit code 0
感谢你提供的输出!
页面返回状态码 200,编码为 utf-8,且源码内容正常,但你贴出的只是前几行,还未包罗文章链接部门。
不过,这已经说明页面没有被重定向或反爬拦截。
调试代码如下:
  1. import requests
  2. from bs4 import BeautifulSoup
  3. url = "http://mysql.taobao.org/monthly/"
  4. headers = {
  5.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  6. }
  7. resp = requests.get(url, headers=headers)
  8. soup = BeautifulSoup(resp.text, "lxml")
  9. # 打印所有a标签的href
  10. for a in soup.find_all("a"):
  11.     print(a.get("href"))
复制代码
请运行上面代码,把输出贴出来,这样我可以帮你精准写出能抓到所有文章链接的正则和爬虫代码。
调试代码返回结果如下

/monthly/
/monthly/2025/04
/monthly/2025/03
/monthly/2025/02
/monthly/2025/01
/monthly/2024/12
/monthly/2024/11
/monthly/2024/10
...
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. BASE_URL = "http://mysql.taobao.org"
  9. MONTHLY_URL = BASE_URL + "/monthly/"
  10. HEADERS = {
  11.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  12. }
  13. SAVE_DIR = "taobao"
  14. MAX_RETRIES = 3
  15. THREADS = 8
  16. os.makedirs(SAVE_DIR, exist_ok=True)
  17. def get_with_retry(url, retries=MAX_RETRIES, **kwargs):
  18.     for i in range(retries):
  19.         try:
  20.             resp = requests.get(url, headers=HEADERS, timeout=10, **kwargs)
  21.             if resp.status_code == 200:
  22.                 return resp
  23.         except Exception:
  24.             if i == retries - 1:
  25.                 raise
  26.             time.sleep(random.uniform(1, 3))
  27.     return None
  28. def get_all_article_links():
  29.     resp = get_with_retry(MONTHLY_URL)
  30.     soup = BeautifulSoup(resp.text, "lxml")
  31.     links = set()
  32.     for a in soup.find_all("a", href=True):
  33.         href = a["href"]
  34.         # 匹配 /monthly/YYYY/MM 目录
  35.         m = re.match(r"/monthly/(\d{4})/(\d{2})$", href)
  36.         if m:
  37.             links.add(BASE_URL + href)
  38.     return sorted(links)
  39. def save_article(url):
  40.     try:
  41.         resp = get_with_retry(url)
  42.         soup = BeautifulSoup(resp.text, "lxml")
  43.         # 获取文章标题
  44.         title = soup.title.text.strip() if soup.title else url.split("/")[-2] + "-" + url.split("/")[-1]
  45.         # 获取正文内容
  46.         content_div = soup.find("div", class_="content")
  47.         if not content_div:
  48.             content_div = soup.find("div", id="content")
  49.         content = content_div.get_text("\n", strip=True) if content_div else soup.get_text("\n", strip=True)
  50.         # 生成markdown内容
  51.         md_content = f"# {title}\n\n原文链接: [{url}]({url})\n\n---\n\n{content}"
  52.         # 文件名
  53.         filename = os.path.join(SAVE_DIR, url.rstrip("/").split("/")[-2] + "-" + url.rstrip("/").split("/")[-1] + ".md")
  54.         with open(filename, "w", encoding="utf-8") as f:
  55.             f.write(md_content)
  56.         print(f"已保存: {filename}")
  57.         time.sleep(random.uniform(0.5, 1.5))
  58.     except Exception as e:
  59.         print(f"下载失败: {url},原因: {e}")
  60. def main():
  61.     links = get_all_article_links()
  62.     print(f"共发现{len(links)}篇文章,开始下载...")
  63.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  64.         futures = [executor.submit(save_article, url) for url in links]
  65.         for future in as_completed(futures):
  66.             pass
  67.     print("全部下载完成。")
  68. if __name__ == "__main__":
  69.     main()
复制代码
三、抓取目次链接而非文章内容

有两个标题
1. 你抓取的文章中文显示有乱码
05
MySQL · 5.7优化 · Metadata Lock子系统的优化

06
MySQL · 5.7特性 · 在线Truncate undo log 表空间

2. 你抓取的是每个目次中的文章链接,而非目次中现实的文章内容

你的反馈非常关键,下面针对两个标题具体分析并给出解决方案:

1. 中文乱码标题

原因分析:

  • 乱码通常是因为 requests 没有正确辨认网页编码,或者网页现实编码与 resp.text 的解码方式不一致。


  • 该站点页面 <meta charset="utf-8">,但有时 requests 辨认不准,需强制设定编码。
解决方法:

  • 在获取响应后,强制设置 resp.encoding = 'utf-8',再用 resp.text。

2. 抓取的是目次中的文章链接,而非现实文章内容

原因分析:

  • 你如今抓到的是每个月的目次页(如 /monthly/2025/04),但现实内容在该目次页的正文部门(通常是 <div class="content">),而不是该页下的其他链接。


  • 你只需抓取每个目次页的正文内容即可,无需再进入子链接。
  • 只抓取每个 /monthly/YYYY/MM 目次页的正文内容。
  • 强制设置编码,解决中文乱码。
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. BASE_URL = "http://mysql.taobao.org"
  9. MONTHLY_URL = BASE_URL + "/monthly/"
  10. HEADERS = {
  11.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  12. }
  13. SAVE_DIR = "taobao"
  14. MAX_RETRIES = 3
  15. THREADS = 8
  16. os.makedirs(SAVE_DIR, exist_ok=True)
  17. def get_with_retry(url, retries=MAX_RETRIES, **kwargs):
  18.     for i in range(retries):
  19.         try:
  20.             resp = requests.get(url, headers=HEADERS, timeout=10, **kwargs)
  21.             resp.encoding = 'utf-8'  # 强制设定编码,防止中文乱码
  22.             if resp.status_code == 200:
  23.                 return resp
  24.         except Exception:
  25.             if i == retries - 1:
  26.                 raise
  27.             time.sleep(random.uniform(1, 3))
  28.     return None
  29. def get_all_article_links():
  30.     resp = get_with_retry(MONTHLY_URL)
  31.     soup = BeautifulSoup(resp.text, "lxml")
  32.     links = set()
  33.     for a in soup.find_all("a", href=True):
  34.         href = a["href"]
  35.         # 匹配 /monthly/YYYY/MM 目录
  36.         m = re.match(r"/monthly/(\d{4})/(\d{2})$", href)
  37.         if m:
  38.             links.add(BASE_URL + href)
  39.     return sorted(links)
  40. def save_article(url):
  41.     try:
  42.         resp = get_with_retry(url)
  43.         soup = BeautifulSoup(resp.text, "lxml")
  44.         # 获取文章标题
  45.         title = soup.title.text.strip() if soup.title else url.split("/")[-2] + "-" + url.split("/")[-1]
  46.         # 获取正文内容
  47.         content_div = soup.find("div", class_="content")
  48.         if not content_div:
  49.             content_div = soup.find("div", id="content")
  50.         if content_div:
  51.             content = content_div.get_text("\n", strip=True)
  52.         else:
  53.             content = soup.get_text("\n", strip=True)
  54.         # 生成markdown内容
  55.         md_content = f"# {title}\n\n原文链接: [{url}]({url})\n\n---\n\n{content}"
  56.         # 文件名
  57.         filename = os.path.join(SAVE_DIR, url.rstrip("/").split("/")[-2] + "-" + url.rstrip("/").split("/")[-1] + ".md")
  58.         with open(filename, "w", encoding="utf-8") as f:
  59.             f.write(md_content)
  60.         print(f"已保存: {filename}")
  61.         time.sleep(random.uniform(0.5, 1.5))
  62.     except Exception as e:
  63.         print(f"下载失败: {url},原因: {e}")
  64. def main():
  65.     links = get_all_article_links()
  66.     print(f"共发现{len(links)}篇文章,开始下载...")
  67.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  68.         futures = [executor.submit(save_article, url) for url in links]
  69.         for future in as_completed(futures):
  70.             pass
  71.     print("全部下载完成。")
  72. if __name__ == "__main__":
  73.     main()
复制代码
3. 需求再拆解

中文显示标题解决了,但抓取的仍旧是目次下的文章标题而非文章内容。这样,你先尝试帮我获取 @http://mysql.taobao.org/monthly/2024/12/ 目次下的文章内容,还是以markdown格式保存 在taobao目次下
  1. import os
  2. import requests
  3. from bs4 import BeautifulSoup
  4. url = "http://mysql.taobao.org/monthly/2024/12/"
  5. headers = {
  6.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  7. }
  8. SAVE_DIR = "taobao"
  9. os.makedirs(SAVE_DIR, exist_ok=True)
  10. resp = requests.get(url, headers=headers, timeout=10)
  11. resp.encoding = 'utf-8'
  12. soup = BeautifulSoup(resp.text, "lxml")
  13. # 获取标题
  14. title = soup.find("h1")
  15. md_content = f"# {title.text.strip() if title else '数据库内核月报'}\n\n原文链接: [{url}]({url})\n\n---\n\n"
  16. # 获取所有条目(通常是h3或h2标题+后续内容)
  17. for tag in soup.find_all(['h3', 'h2']):
  18.     # 只抓取以“# XX”开头的条目
  19.     if tag.text.strip().startswith("#"):
  20.         md_content += f"## {tag.text.strip()}\n\n"
  21.         # 收集该标题下的所有段落,直到下一个h3/h2
  22.         for sibling in tag.find_next_siblings():
  23.             if sibling.name in ['h3', 'h2']:
  24.                 break
  25.             # 只抓正文段落和列表
  26.             if sibling.name in ['p', 'ul', 'ol', 'pre', 'blockquote']:
  27.                 md_content += sibling.get_text("\n", strip=True) + "\n\n"
  28. filename = os.path.join(SAVE_DIR, "2024-12.md")
  29. with open(filename, "w", encoding="utf-8") as f:
  30.     f.write(md_content)
  31. print(f"已保存: {filename}")
复制代码
还是有标题,你并没有打开目次中的子链接,比方第一篇文章打开后是 @http://mysql.taobao.org/monthly/2024/12/01/ ,你应该逐个打开链接并获取文章内容
  1. import os
  2. import re
  3. import requests
  4. from bs4 import BeautifulSoup
  5. BASE_URL = "http://mysql.taobao.org"
  6. MONTH_URL = "/monthly/2024/12/"
  7. FULL_MONTH_URL = BASE_URL + MONTH_URL
  8. HEADERS = {
  9.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  10. }
  11. SAVE_DIR = "taobao"
  12. os.makedirs(SAVE_DIR, exist_ok=True)
  13. def get_soup(url):
  14.     resp = requests.get(url, headers=HEADERS, timeout=10)
  15.     resp.encoding = 'utf-8'
  16.     return BeautifulSoup(resp.text, "lxml")
  17. def get_article_links(month_url):
  18.     soup = get_soup(month_url)
  19.     links = []
  20.     for a in soup.find_all("a", href=True):
  21.         href = a["href"]
  22.         # 匹配 /monthly/2024/12/01/ 这种子文章链接
  23.         if re.match(r"/monthly/2024/12/\d{2}/$", href):
  24.             # 绝对化
  25.             if not href.startswith("http"):
  26.                 href = BASE_URL + href
  27.             links.append(href)
  28.     return sorted(set(links))
  29. def get_article_content(article_url):
  30.     soup = get_soup(article_url)
  31.     # 标题
  32.     h2 = soup.find("h2")
  33.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  34.     # 正文
  35.     content_div = soup.find("div", class_="content")
  36.     if not content_div:
  37.         content_div = soup.find("div", id="content")
  38.     content = content_div.get_text("\n", strip=True) if content_div else soup.get_text("\n", strip=True)
  39.     return f"## {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n\n---\n"
  40. def main():
  41.     article_links = get_article_links(FULL_MONTH_URL)
  42.     print(f"共发现{len(article_links)}篇文章")
  43.     md_content = f"# 数据库内核月报 2024/12\n\n原文目录: [{FULL_MONTH_URL}]({FULL_MONTH_URL})\n\n---\n\n"
  44.     for link in article_links:
  45.         print(f"抓取: {link}")
  46.         md_content += get_article_content(link)
  47.     filename = os.path.join(SAVE_DIR, "2024-12.md")
  48.     with open(filename, "w", encoding="utf-8") as f:
  49.         f.write(md_content)
  50.     print(f"已保存: {filename}")
  51. if __name__ == "__main__":
  52.     main()
复制代码
就是这样,干得漂亮~ 但你需要将每篇文章单独存放在 .md文件里,并以文章标题作为文件名,加油
  1. import os
  2. import re
  3. import requests
  4. from bs4 import BeautifulSoup
  5. BASE_URL = "http://mysql.taobao.org"
  6. MONTH_URL = "/monthly/2024/12/"
  7. FULL_MONTH_URL = BASE_URL + MONTH_URL
  8. HEADERS = {
  9.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  10. }
  11. SAVE_DIR = "taobao"
  12. os.makedirs(SAVE_DIR, exist_ok=True)
  13. def safe_filename(title):
  14.     # 只保留中英文、数字、下划线和横线
  15.     return re.sub(r'[\\/:*?"<>|]', '', title).strip()
  16. def get_soup(url):
  17.     resp = requests.get(url, headers=HEADERS, timeout=10)
  18.     resp.encoding = 'utf-8'
  19.     return BeautifulSoup(resp.text, "lxml")
  20. def get_article_links(month_url):
  21.     soup = get_soup(month_url)
  22.     links = []
  23.     for a in soup.find_all("a", href=True):
  24.         href = a["href"]
  25.         # 匹配 /monthly/2024/12/01/ 这种子文章链接
  26.         if re.match(r"/monthly/2024/12/\d{2}/$", href):
  27.             # 绝对化
  28.             if not href.startswith("http"):
  29.                 href = BASE_URL + href
  30.             links.append(href)
  31.     return sorted(set(links))
  32. def save_article(article_url):
  33.     soup = get_soup(article_url)
  34.     # 标题
  35.     h2 = soup.find("h2")
  36.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  37.     filename = safe_filename(title) + ".md"
  38.     # 正文
  39.     content_div = soup.find("div", class_="content")
  40.     if not content_div:
  41.         content_div = soup.find("div", id="content")
  42.     content = content_div.get_text("\n", strip=True) if content_div else soup.get_text("\n", strip=True)
  43.     md_content = f"# {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n"
  44.     filepath = os.path.join(SAVE_DIR, filename)
  45.     with open(filepath, "w", encoding="utf-8") as f:
  46.         f.write(md_content)
  47.     print(f"已保存: {filepath}")
  48. def main():
  49.     article_links = get_article_links(FULL_MONTH_URL)
  50.     print(f"共发现{len(article_links)}篇文章")
  51.     for link in article_links:
  52.         save_article(link)
  53.     print("全部下载完成。")
  54. if __name__ == "__main__":
  55.     main()
复制代码


四、 格式化内容

内容ok,帮我再格式化一下内里的文本和代码,使其比较雅观
  1. import os
  2. import re
  3. import requests
  4. from bs4 import BeautifulSoup
  5. BASE_URL = "http://mysql.taobao.org"
  6. MONTH_URL = "/monthly/2024/12/"
  7. FULL_MONTH_URL = BASE_URL + MONTH_URL
  8. HEADERS = {
  9.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  10. }
  11. SAVE_DIR = "taobao"
  12. os.makedirs(SAVE_DIR, exist_ok=True)
  13. def safe_filename(title):
  14.     # 只保留中英文、数字、下划线和横线
  15.     return re.sub(r'[\\/:*?"<>|]', '', title).strip()
  16. def get_soup(url):
  17.     resp = requests.get(url, headers=HEADERS, timeout=10)
  18.     resp.encoding = 'utf-8'
  19.     return BeautifulSoup(resp.text, "lxml")
  20. def get_article_links(month_url):
  21.     soup = get_soup(month_url)
  22.     links = []
  23.     for a in soup.find_all("a", href=True):
  24.         href = a["href"]
  25.         # 匹配 /monthly/2024/12/01/ 这种子文章链接
  26.         if re.match(r"/monthly/2024/12/\d{2}/$", href):
  27.             # 绝对化
  28.             if not href.startswith("http"):
  29.                 href = BASE_URL + href
  30.             links.append(href)
  31.     return sorted(set(links))
  32. def format_content(content_div):
  33.     md_lines = []
  34.     for elem in content_div.children:
  35.         if elem.name == 'pre':
  36.             # 代码块
  37.             code = elem.get_text("\n", strip=True)
  38.             md_lines.append(f'\n```\n{code}\n```\n')
  39.         elif elem.name in ['ul', 'ol']:
  40.             # 列表
  41.             for li in elem.find_all('li'):
  42.                 md_lines.append(f'- {li.get_text(strip=True)}')
  43.             md_lines.append('')
  44.         elif elem.name == 'blockquote':
  45.             # 引用
  46.             quote = elem.get_text("\n", strip=True)
  47.             md_lines.append(f'> {quote}\n')
  48.         elif elem.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
  49.             # 标题
  50.             level = int(elem.name[1])
  51.             md_lines.append(f'\n{"#"*level} {elem.get_text(strip=True)}\n')
  52.         elif elem.name == 'p' or elem.name is None:
  53.             # 普通段落
  54.             text = elem.get_text("\n", strip=True)
  55.             if text:
  56.                 md_lines.append(f'{text}\n')
  57.     return '\n'.join(md_lines)
  58. def save_article(article_url):
  59.     soup = get_soup(article_url)
  60.     # 标题
  61.     h2 = soup.find("h2")
  62.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  63.     filename = safe_filename(title) + ".md"
  64.     # 正文
  65.     content_div = soup.find("div", class_="content")
  66.     if not content_div:
  67.         content_div = soup.find("div", id="content")
  68.     if content_div:
  69.         content = format_content(content_div)
  70.     else:
  71.         content = soup.get_text("\n", strip=True)
  72.     md_content = f"# {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n"
  73.     filepath = os.path.join(SAVE_DIR, filename)
  74.     with open(filepath, "w", encoding="utf-8") as f:
  75.         f.write(md_content)
  76.     print(f"已保存: {filepath}")
  77. def main():
  78.     article_links = get_article_links(FULL_MONTH_URL)
  79.     print(f"共发现{len(article_links)}篇文章")
  80.     for link in article_links:
  81.         save_article(link)
  82.     print("全部下载完成。")
  83. if __name__ == "__main__":
  84.     main()
复制代码


五、 合并代码,实现初始需求

嗯,就是这样,联合以上思路,回到我们最开始的需求,帮我抓取获取 http://mysql.taobao.org/monthly/ 中的所有文章链接,并将文档内容以markdown格式存放在当地taobao目次中,其余要求不变
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. BASE_URL = "http://mysql.taobao.org"
  9. MONTHLY_URL = BASE_URL + "/monthly/"
  10. HEADERS = {
  11.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  12. }
  13. SAVE_DIR = "taobao"
  14. THREADS = 8
  15. os.makedirs(SAVE_DIR, exist_ok=True)
  16. def safe_filename(title):
  17.     return re.sub(r'[\\/:*?"<>|]', '', title).strip()
  18. def get_soup(url):
  19.     for _ in range(3):
  20.         try:
  21.             resp = requests.get(url, headers=HEADERS, timeout=10)
  22.             resp.encoding = 'utf-8'
  23.             if resp.status_code == 200:
  24.                 return BeautifulSoup(resp.text, "lxml")
  25.         except Exception:
  26.             time.sleep(random.uniform(1, 2))
  27.     return None
  28. def get_all_month_links():
  29.     soup = get_soup(MONTHLY_URL)
  30.     links = set()
  31.     for a in soup.find_all("a", href=True):
  32.         href = a["href"]
  33.         # 匹配 /monthly/YYYY/MM 目录
  34.         m = re.match(r"/monthly/(\d{4})/(\d{2})$", href)
  35.         if m:
  36.             links.add(BASE_URL + href + "/")
  37.     return sorted(links)
  38. def get_article_links(month_url):
  39.     soup = get_soup(month_url)
  40.     links = []
  41.     if not soup:
  42.         return links
  43.     for a in soup.find_all("a", href=True):
  44.         href = a["href"]
  45.         # 匹配 /monthly/YYYY/MM/XX/ 这种子文章链接
  46.         m = re.match(r"/monthly/\d{4}/\d{2}/\d{2}/$", href)
  47.         if m:
  48.             if not href.startswith("http"):
  49.                 href = BASE_URL + href
  50.             links.append(href)
  51.     return sorted(set(links))
  52. def format_content(content_div):
  53.     md_lines = []
  54.     for elem in content_div.children:
  55.         if elem.name == 'pre':
  56.             code = elem.get_text("\n", strip=True)
  57.             md_lines.append(f'\n```\n{code}\n```\n')
  58.         elif elem.name in ['ul', 'ol']:
  59.             for li in elem.find_all('li'):
  60.                 md_lines.append(f'- {li.get_text(strip=True)}')
  61.             md_lines.append('')
  62.         elif elem.name == 'blockquote':
  63.             quote = elem.get_text("\n", strip=True)
  64.             md_lines.append(f'> {quote}\n')
  65.         elif elem.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
  66.             level = int(elem.name[1])
  67.             md_lines.append(f'\n{"#"*level} {elem.get_text(strip=True)}\n')
  68.         elif elem.name == 'p' or elem.name is None:
  69.             text = elem.get_text("\n", strip=True)
  70.             if text:
  71.                 md_lines.append(f'{text}\n')
  72.     return '\n'.join(md_lines)
  73. def save_article(article_url):
  74.     soup = get_soup(article_url)
  75.     if not soup:
  76.         print(f"获取失败: {article_url}")
  77.         return
  78.     h2 = soup.find("h2")
  79.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  80.     filename = safe_filename(title) + ".md"
  81.     content_div = soup.find("div", class_="content")
  82.     if not content_div:
  83.         content_div = soup.find("div", id="content")
  84.     if content_div:
  85.         content = format_content(content_div)
  86.     else:
  87.         content = soup.get_text("\n", strip=True)
  88.     md_content = f"# {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n"
  89.     filepath = os.path.join(SAVE_DIR, filename)
  90.     with open(filepath, "w", encoding="utf-8") as f:
  91.         f.write(md_content)
  92.     print(f"已保存: {filepath}")
  93.     time.sleep(random.uniform(0.5, 1.2))
  94. def main():
  95.     month_links = get_all_month_links()
  96.     print(f"共发现{len(month_links)}个月份")
  97.     all_article_links = []
  98.     for month_url in month_links:
  99.         article_links = get_article_links(month_url)
  100.         print(f"{month_url} 发现{len(article_links)}篇文章")
  101.         all_article_links.extend(article_links)
  102.     print(f"总共发现{len(all_article_links)}篇文章,开始下载...")
  103.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  104.         futures = [executor.submit(save_article, url) for url in all_article_links]
  105.         for future in as_completed(futures):
  106.             pass
  107.     print("全部下载完成。")
  108. if __name__ == "__main__":
  109.     main()
复制代码
六、 目次细化,抓取图片

干得漂亮~ 需求已基本实现,有一些优化项:
1. 帮我按照链接中的年份及月份创建子目次,并将对应月份的文章放入此中
2. 只需要抓取2024及2025年的文章
3. 如果文章中有表格、图片等,也需要抓取下来
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. from urllib.parse import urljoin, urlparse
  9. BASE_URL = "http://mysql.taobao.org"
  10. MONTHLY_URL = BASE_URL + "/monthly/"
  11. HEADERS = {
  12.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  13. }
  14. SAVE_DIR = "taobao"
  15. THREADS = 8
  16. os.makedirs(SAVE_DIR, exist_ok=True)
  17. def safe_filename(title):
  18.     return re.sub(r'[\\/:*?"<>|]', '', title).strip()
  19. def get_soup(url):
  20.     for _ in range(3):
  21.         try:
  22.             resp = requests.get(url, headers=HEADERS, timeout=10)
  23.             resp.encoding = 'utf-8'
  24.             if resp.status_code == 200:
  25.                 return BeautifulSoup(resp.text, "lxml")
  26.         except Exception:
  27.             time.sleep(random.uniform(1, 2))
  28.     return None
  29. def get_all_month_links():
  30.     soup = get_soup(MONTHLY_URL)
  31.     links = set()
  32.     for a in soup.find_all("a", href=True):
  33.         href = a["href"]
  34.         # 只抓2024和2025年
  35.         m = re.match(r"/monthly/(2024|2025)/(\d{2})$", href)
  36.         if m:
  37.             links.add(BASE_URL + href + "/")
  38.     return sorted(links)
  39. def get_article_links(month_url):
  40.     soup = get_soup(month_url)
  41.     links = []
  42.     if not soup:
  43.         return links
  44.     for a in soup.find_all("a", href=True):
  45.         href = a["href"]
  46.         # 匹配 /monthly/2024/12/01/ 这种子文章链接
  47.         m = re.match(r"/monthly/(2024|2025)/\d{2}/\d{2}/$", href)
  48.         if m:
  49.             if not href.startswith("http"):
  50.                 href = BASE_URL + href
  51.             links.append(href)
  52.     return sorted(set(links))
  53. def download_image(img_url, save_path):
  54.     try:
  55.         resp = requests.get(img_url, headers=HEADERS, timeout=10)
  56.         if resp.status_code == 200:
  57.             with open(save_path, 'wb') as f:
  58.                 f.write(resp.content)
  59.             return True
  60.     except Exception:
  61.         pass
  62.     return False
  63. def format_table(table):
  64.     rows = table.find_all('tr')
  65.     md = []
  66.     for i, row in enumerate(rows):
  67.         cols = row.find_all(['td', 'th'])
  68.         line = '| ' + ' | '.join(col.get_text(strip=True) for col in cols) + ' |'
  69.         md.append(line)
  70.         if i == 0:
  71.             md.append('|' + '---|' * len(cols))
  72.     return '\n'.join(md) + '\n'
  73. def format_content(content_div, article_url, save_dir):
  74.     md_lines = []
  75.     for elem in content_div.children:
  76.         if elem.name == 'pre':
  77.             code = elem.get_text("\n", strip=True)
  78.             md_lines.append(f'\n```\n{code}\n```\n')
  79.         elif elem.name in ['ul', 'ol']:
  80.             for li in elem.find_all('li'):
  81.                 md_lines.append(f'- {li.get_text(strip=True)}')
  82.             md_lines.append('')
  83.         elif elem.name == 'blockquote':
  84.             quote = elem.get_text("\n", strip=True)
  85.             md_lines.append(f'> {quote}\n')
  86.         elif elem.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
  87.             level = int(elem.name[1])
  88.             md_lines.append(f'\n{"#"*level} {elem.get_text(strip=True)}\n')
  89.         elif elem.name == 'p' or elem.name is None:
  90.             text = elem.get_text("\n", strip=True)
  91.             if text:
  92.                 md_lines.append(f'{text}\n')
  93.         elif elem.name == 'table':
  94.             md_lines.append(format_table(elem))
  95.         elif elem.name == 'img':
  96.             img_src = elem.get('src')
  97.             if img_src:
  98.                 img_url = urljoin(article_url, img_src)
  99.                 img_name = os.path.basename(urlparse(img_url).path)
  100.                 img_save_path = os.path.join(save_dir, img_name)
  101.                 if download_image(img_url, img_save_path):
  102.                     md_lines.append(f'![{img_name}]({img_name})\n')
  103.     # 处理段落内图片
  104.     for img in content_div.find_all('img'):
  105.         img_src = img.get('src')
  106.         if img_src:
  107.             img_url = urljoin(article_url, img_src)
  108.             img_name = os.path.basename(urlparse(img_url).path)
  109.             img_save_path = os.path.join(save_dir, img_name)
  110.             if download_image(img_url, img_save_path):
  111.                 # 替换原img为markdown图片语法
  112.                 img.replace_with(f'![{img_name}]({img_name})')
  113.     return '\n'.join(md_lines)
  114. def save_article(article_url, year, month):
  115.     soup = get_soup(article_url)
  116.     if not soup:
  117.         print(f"获取失败: {article_url}")
  118.         return
  119.     h2 = soup.find("h2")
  120.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  121.     filename = safe_filename(title) + ".md"
  122.     # 按年份和月份创建子目录
  123.     subdir = os.path.join(SAVE_DIR, year, month)
  124.     os.makedirs(subdir, exist_ok=True)
  125.     content_div = soup.find("div", class_="content")
  126.     if not content_div:
  127.         content_div = soup.find("div", id="content")
  128.     if content_div:
  129.         content = format_content(content_div, article_url, subdir)
  130.     else:
  131.         content = soup.get_text("\n", strip=True)
  132.     md_content = f"# {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n"
  133.     filepath = os.path.join(subdir, filename)
  134.     with open(filepath, "w", encoding="utf-8") as f:
  135.         f.write(md_content)
  136.     print(f"已保存: {filepath}")
  137.     time.sleep(random.uniform(0.5, 1.2))
  138. def main():
  139.     month_links = get_all_month_links()
  140.     print(f"共发现{len(month_links)}个月份")
  141.     all_tasks = []
  142.     for month_url in month_links:
  143.         m = re.match(r".*/monthly/(2024|2025)/(\d{2})/", month_url)
  144.         if not m:
  145.             continue
  146.         year, month = m.group(1), m.group(2)
  147.         article_links = get_article_links(month_url)
  148.         print(f"{month_url} 发现{len(article_links)}篇文章")
  149.         for link in article_links:
  150.             all_tasks.append((link, year, month))
  151.     print(f"总共发现{len(all_tasks)}篇文章,开始下载...")
  152.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  153.         futures = [executor.submit(save_article, url, year, month) for url, year, month in all_tasks]
  154.         for future in as_completed(futures):
  155.             pass
  156.     print("全部下载完成。")
  157. if __name__ == "__main__":
  158.     main()
复制代码
嗯,图片不要单独保存,需要嵌入回.md文档里,这样方便用户查询
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. from urllib.parse import urljoin
  9. BASE_URL = "http://mysql.taobao.org"
  10. MONTHLY_URL = BASE_URL + "/monthly/"
  11. HEADERS = {
  12.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  13. }
  14. SAVE_DIR = "taobao"
  15. THREADS = 8
  16. os.makedirs(SAVE_DIR, exist_ok=True)
  17. def safe_filename(title):
  18.     return re.sub(r'[\\/:*?"<>|]', '', title).strip()
  19. def get_soup(url):
  20.     for _ in range(3):
  21.         try:
  22.             resp = requests.get(url, headers=HEADERS, timeout=10)
  23.             resp.encoding = 'utf-8'
  24.             if resp.status_code == 200:
  25.                 return BeautifulSoup(resp.text, "lxml")
  26.         except Exception:
  27.             time.sleep(random.uniform(1, 2))
  28.     return None
  29. def get_all_month_links():
  30.     soup = get_soup(MONTHLY_URL)
  31.     links = set()
  32.     for a in soup.find_all("a", href=True):
  33.         href = a["href"]
  34.         m = re.match(r"/monthly/(2024|2025)/(\d{2})$", href)
  35.         if m:
  36.             links.add(BASE_URL + href + "/")
  37.     return sorted(links)
  38. def get_article_links(month_url):
  39.     soup = get_soup(month_url)
  40.     links = []
  41.     if not soup:
  42.         return links
  43.     for a in soup.find_all("a", href=True):
  44.         href = a["href"]
  45.         m = re.match(r"/monthly/(2024|2025)/\d{2}/\d{2}/$", href)
  46.         if m:
  47.             if not href.startswith("http"):
  48.                 href = BASE_URL + href
  49.             links.append(href)
  50.     return sorted(set(links))
  51. def format_table(table):
  52.     rows = table.find_all('tr')
  53.     md = []
  54.     for i, row in enumerate(rows):
  55.         cols = row.find_all(['td', 'th'])
  56.         line = '| ' + ' | '.join(col.get_text(strip=True) for col in cols) + ' |'
  57.         md.append(line)
  58.         if i == 0:
  59.             md.append('|' + '---|' * len(cols))
  60.     return '\n'.join(md) + '\n'
  61. def format_content(content_div, article_url):
  62.     md_lines = []
  63.     for elem in content_div.children:
  64.         if elem.name == 'pre':
  65.             code = elem.get_text("\n", strip=True)
  66.             md_lines.append(f'\n```\n{code}\n```\n')
  67.         elif elem.name in ['ul', 'ol']:
  68.             for li in elem.find_all('li'):
  69.                 md_lines.append(f'- {li.get_text(strip=True)}')
  70.             md_lines.append('')
  71.         elif elem.name == 'blockquote':
  72.             quote = elem.get_text("\n", strip=True)
  73.             md_lines.append(f'> {quote}\n')
  74.         elif elem.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
  75.             level = int(elem.name[1])
  76.             md_lines.append(f'\n{"#"*level} {elem.get_text(strip=True)}\n')
  77.         elif elem.name == 'p' or elem.name is None:
  78.             text = elem.get_text("\n", strip=True)
  79.             if text:
  80.                 md_lines.append(f'{text}\n')
  81.         elif elem.name == 'table':
  82.             md_lines.append(format_table(elem))
  83.         elif elem.name == 'img':
  84.             img_src = elem.get('src')
  85.             if img_src:
  86.                 img_url = urljoin(article_url, img_src)
  87.                 md_lines.append(f'![image]({img_url})\n')
  88.     # 处理段落内图片
  89.     for img in content_div.find_all('img'):
  90.         img_src = img.get('src')
  91.         if img_src:
  92.             img_url = urljoin(article_url, img_src)
  93.             img.replace_with(f'![image]({img_url})')
  94.     return '\n'.join(md_lines)
  95. def save_article(article_url, year, month):
  96.     soup = get_soup(article_url)
  97.     if not soup:
  98.         print(f"获取失败: {article_url}")
  99.         return
  100.     h2 = soup.find("h2")
  101.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  102.     filename = safe_filename(title) + ".md"
  103.     subdir = os.path.join(SAVE_DIR, year, month)
  104.     os.makedirs(subdir, exist_ok=True)
  105.     content_div = soup.find("div", class_="content")
  106.     if not content_div:
  107.         content_div = soup.find("div", id="content")
  108.     if content_div:
  109.         content = format_content(content_div, article_url)
  110.     else:
  111.         content = soup.get_text("\n", strip=True)
  112.     md_content = f"# {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n"
  113.     filepath = os.path.join(subdir, filename)
  114.     with open(filepath, "w", encoding="utf-8") as f:
  115.         f.write(md_content)
  116.     print(f"已保存: {filepath}")
  117.     time.sleep(random.uniform(0.5, 1.2))
  118. def main():
  119.     month_links = get_all_month_links()
  120.     print(f"共发现{len(month_links)}个月份")
  121.     all_tasks = []
  122.     for month_url in month_links:
  123.         m = re.match(r".*/monthly/(2024|2025)/(\d{2})/", month_url)
  124.         if not m:
  125.             continue
  126.         year, month = m.group(1), m.group(2)
  127.         article_links = get_article_links(month_url)
  128.         print(f"{month_url} 发现{len(article_links)}篇文章")
  129.         for link in article_links:
  130.             all_tasks.append((link, year, month))
  131.     print(f"总共发现{len(all_tasks)}篇文章,开始下载...")
  132.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  133.         futures = [executor.submit(save_article, url, year, month) for url, year, month in all_tasks]
  134.         for future in as_completed(futures):
  135.             pass
  136.     print("全部下载完成。")
  137. if __name__ == "__main__":
  138.     main()
复制代码
图片没有显示成功呢



联合你这个优化思路,再优化下之前那段代码,使抓取的图片能在.md文件中直接展示
  1. import os
  2. import re
  3. import time
  4. import random
  5. import requests
  6. from bs4 import BeautifulSoup
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. from urllib.parse import urljoin
  9. BASE_URL = "http://mysql.taobao.org"
  10. MONTHLY_URL = BASE_URL + "/monthly/"
  11. HEADERS = {
  12.     "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
  13. }
  14. SAVE_DIR = "taobao"
  15. THREADS = 8
  16. os.makedirs(SAVE_DIR, exist_ok=True)
  17. def safe_filename(title):
  18.     # 只保留中英文、数字、下划线和横线
  19.     return re.sub(r'[\\/:*?"<>|]', '', title).strip()
  20. def get_soup(url):
  21.     for _ in range(3):
  22.         try:
  23.             resp = requests.get(url, headers=HEADERS, timeout=10)
  24.             resp.encoding = 'utf-8'
  25.             if resp.status_code == 200:
  26.                 return BeautifulSoup(resp.text, "lxml")
  27.         except Exception:
  28.             time.sleep(random.uniform(1, 2))
  29.     return None
  30. def get_all_month_links():
  31.     soup = get_soup(MONTHLY_URL)
  32.     links = set()
  33.     for a in soup.find_all("a", href=True):
  34.         href = a["href"]
  35.         # 只抓2024和2025年
  36.         m = re.match(r"/monthly/(2024|2025)/(\d{2})$", href)
  37.         if m:
  38.             links.add(BASE_URL + href + "/")
  39.     return sorted(links)
  40. def get_article_links(month_url):
  41.     soup = get_soup(month_url)
  42.     links = []
  43.     if not soup:
  44.         return links
  45.     for a in soup.find_all("a", href=True):
  46.         href = a["href"]
  47.         # 匹配 /monthly/2024/12/01/ 这种子文章链接
  48.         m = re.match(r"/monthly/(2024|2025)/\d{2}/\d{2}/$", href)
  49.         if m:
  50.             if not href.startswith("http"):
  51.                 href = BASE_URL + href
  52.             links.append(href)
  53.     return sorted(set(links))
  54. def format_table(table):
  55.     rows = table.find_all('tr')
  56.     md = []
  57.     for i, row in enumerate(rows):
  58.         cols = row.find_all(['td', 'th'])
  59.         line = '| ' + ' | '.join(col.get_text(strip=True) for col in cols) + ' |'
  60.         md.append(line)
  61.         if i == 0:
  62.             md.append('|' + '---|' * len(cols))
  63.     return '\n'.join(md) + '\n'
  64. def format_content(content_div, article_url):
  65.     md_lines = []
  66.     for elem in content_div.children:
  67.         if elem.name == 'pre':
  68.             code = elem.get_text("\n", strip=True)
  69.             md_lines.append(f'\n```\n{code}\n```\n')
  70.         elif elem.name in ['ul', 'ol']:
  71.             for li in elem.find_all('li', recursive=False):
  72.                 # 处理列表项中的图片
  73.                 imgs = li.find_all('img')
  74.                 for img in imgs:
  75.                     img_src = img.get('src')
  76.                     if img_src:
  77.                         img_url = urljoin(article_url, img_src)
  78.                         md_lines.append(f'![image]({img_url})')
  79.                 text = li.get_text(" ", strip=True)
  80.                 if text:
  81.                     md_lines.append(f'- {text}')
  82.             md_lines.append('')
  83.         elif elem.name == 'blockquote':
  84.             quote = elem.get_text("\n", strip=True)
  85.             md_lines.append(f'> {quote}\n')
  86.         elif elem.name in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
  87.             level = int(elem.name[1])
  88.             md_lines.append(f'\n{"#"*level} {elem.get_text(strip=True)}\n')
  89.         elif elem.name == 'p' or elem.name is None:
  90.             # 处理段落中的图片
  91.             if hasattr(elem, 'find_all'):
  92.                 imgs = elem.find_all('img')
  93.                 for img in imgs:
  94.                     img_src = img.get('src')
  95.                     if img_src:
  96.                         img_url = urljoin(article_url, img_src)
  97.                         md_lines.append(f'![image]({img_url})')
  98.             text = elem.get_text("\n", strip=True)
  99.             if text:
  100.                 md_lines.append(f'{text}\n')
  101.         elif elem.name == 'table':
  102.             md_lines.append(format_table(elem))
  103.     return '\n'.join(md_lines)
  104. def save_article(article_url, year, month):
  105.     soup = get_soup(article_url)
  106.     if not soup:
  107.         print(f"获取失败: {article_url}")
  108.         return
  109.     h2 = soup.find("h2")
  110.     title = h2.text.strip() if h2 else article_url.split("/")[-2]
  111.     filename = safe_filename(title) + ".md"
  112.     # 按年份和月份创建子目录
  113.     subdir = os.path.join(SAVE_DIR, year, month)
  114.     os.makedirs(subdir, exist_ok=True)
  115.     content_div = soup.find("div", class_="content")
  116.     if not content_div:
  117.         content_div = soup.find("div", id="content")
  118.     if content_div:
  119.         content = format_content(content_div, article_url)
  120.     else:
  121.         content = soup.get_text("\n", strip=True)
  122.     md_content = f"# {title}\n\n原文链接: [{article_url}]({article_url})\n\n{content}\n"
  123.     filepath = os.path.join(subdir, filename)
  124.     with open(filepath, "w", encoding="utf-8") as f:
  125.         f.write(md_content)
  126.     print(f"已保存: {filepath}")
  127.     time.sleep(random.uniform(0.5, 1.2))
  128. def main():
  129.     month_links = get_all_month_links()
  130.     print(f"共发现{len(month_links)}个月份")
  131.     all_tasks = []
  132.     for month_url in month_links:
  133.         m = re.match(r".*/monthly/(2024|2025)/(\d{2})/", month_url)
  134.         if not m:
  135.             continue
  136.         year, month = m.group(1), m.group(2)
  137.         article_links = get_article_links(month_url)
  138.         print(f"{month_url} 发现{len(article_links)}篇文章")
  139.         for link in article_links:
  140.             all_tasks.append((link, year, month))
  141.     print(f"总共发现{len(all_tasks)}篇文章,开始下载...")
  142.     with ThreadPoolExecutor(max_workers=THREADS) as executor:
  143.         futures = [executor.submit(save_article, url, year, month) for url, year, month in all_tasks]
  144.         for future in as_completed(futures):
  145.             pass
  146.     print("全部下载完成。")
  147. if __name__ == "__main__":
  148.     main()
复制代码
终于终于,需求实现~

七、 实行结果

抓取成功~

文章结果


八、 最佳提示词总结

总结我们的对话,我应该给你什么样的提示词,能让你在第一次就天生这段目标代码呢
最佳提示词(Prompt)示例

你是一个Python爬虫专家。请帮我用Python编写一个高效的爬虫,要求如下:

  • 目标网站:抓取 http://mysql.taobao.org/monthly/ 下2024和2025年所有月报文章。

  • 目次结构:自动辨认所有2024和2025年下的月份和文章链接,并按“年份/月份”创建当地子目次(如 taobao/2024/12/)。

  • 内容保存:每篇文章内容以 markdown 格式单独保存为 .md 文件,文件名为文章标题,放在对应的子目次下。

  • 内容格式:正文内容需雅观排版,支持代码块、表格(转为markdown表格)、列表、引用等常见格式。

  • 图片处理:文章中的图片在 markdown 文件中以原始网络URL嵌入(即 ![alt](url)),无需下载到当地,确保在md预览时可直接显示。

  • 服从与反爬:支持多线程下载,设置User-Agent、随机延时、自动重试等反爬步伐。

  • 健壮性:遇到网络异常、页面结构变化等情况能自动跳过并继续。

  • 依靠说明:请注明所需的第三方库。
请直接给出完整、可运行的Python代码。

这样写的好处


  • 明确了目标范围(2024/2025年、所有月报、所有文章)


  • 明确了目次结构和文件定名


  • 明确了内容格式和图片处理方式


  • 明确了服从和反爬要求


  • 明确了健壮性和依靠

你可以根据现实需求增减细节,但只要覆盖上述要点,基本可以一次性获得你想要的高质量爬虫代码!

九、 知识点学习

为了实现这个需求,你的终极代码天生思路是什么样的呢,帮助一步步列出。这段代码中的重点和难点分别是什么,涉及到哪些知识点?帮助联合代码分析,我不太清晰你的步骤对应代码中的哪些内容


1. 步骤分解与代码对应









2. 重点与难点代码分析

重点1:高质量内容解析与格式化


  • format_content 函数负责将HTML正文转为雅观的Markdown,兼容各种标签(段落、标题、代码块、表格、图片等)。


  • format_table 函数将HTML表格转为Markdown表格。
难点1:图片URL处理


  • 用 urljoin(article_url, img_src) 保证图片URL完整,插入 ![image](url),确保md中能直接显示图片。
难点2:多线程与异常处理


  • ThreadPoolExecutor 并发抓取,get_soup 内部自动重试,保证大批量抓取时的健壮性。
难点3:文件名正当性


  • safe_filename 函数去除标题中的非法字符,避免文件保存出错。

3. 涉及知识点与代码位置


  • requests/BeautifulSoup:所有网页哀求与HTML解析相干函数


  • 正则表达式:链接筛选、文件名处理


  • os/path:目次和文件操作


  • concurrent.futures:多线程并发


  • Markdown语法:format_content、format_table等


  • 异常处理:try/except、重试机制

4. 总结


  • 你可以对照每个函数和代码块,明白它在整个流程中的作用。


  • 重点和难点都在内容解析、图片处理、多线程和健壮性上,代码中都有明确实现。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表