鱼C论坛

 找回密码
 立即注册
查看: 217|回复: 3

[技术交流] 写了个笔趣阁爬书的小程序

[复制链接]
发表于 2025-9-8 20:04:59 | 显示全部楼层
shadowmage 发表于 2025-09-08 20:03
@FishC
我想要提高代码效率,想重新把上面代码重构, ...


当然可以!将代码按照功能分层是一个很好的做法,这样不仅提高了代码的可读性和可维护性,还能方便后续扩展和优化。下面我将为你提供一个基于 crawler / parser / store / scheduler 四层架构的简单示例,使用 Python 实现。


  1. import requests
  2. from bs4 import BeautifulSoup
  3. import sqlite3
  4. import os
  5. from urllib.parse import urljoin, urlparse
  6. import time
  7. from queue import Queue
  8. from threading import Thread

  9. # ==================== Crawler 层 ====================
  10. class Crawler:
  11.     def fetch(self, url):
  12.         try:
  13.             response = requests.get(url, timeout=10)
  14.             response.raise_for_status()
  15.             return response.text
  16.         except requests.RequestException as e:
  17.             print(f"抓取失败: {url}, 错误: {e}")
  18.             return None

  19. # ==================== Parser 层 ====================
  20. class Parser:
  21.     def parse(self, html, base_url):
  22.         if not html:
  23.             return None
  24.         soup = BeautifulSoup(html, 'html.parser')
  25.         # 示例:提取标题和所有链接
  26.         title = soup.title.string if soup.title else "无标题"
  27.         links = [urljoin(base_url, a.get('href')) for a in soup.find_all('a', href=True)]
  28.         return {
  29.             'title': title,
  30.             'links': links
  31.         }

  32. # ==================== Store 层 ====================
  33. class Store:
  34.     def __init__(self, db_path='data.db', storage_dir='pages'):
  35.         self.db_path = db_path
  36.         self.storage_dir = storage_dir
  37.         os.makedirs(storage_dir, exist_ok=True)
  38.         self.init_db()

  39.     def init_db(self):
  40.         conn = sqlite3.connect(self.db_path)
  41.         cursor = conn.cursor()
  42.         cursor.execute('''
  43.             CREATE TABLE IF NOT EXISTS pages (
  44.                 url TEXT PRIMARY KEY,
  45.                 title TEXT,
  46.                 file_path TEXT,
  47.                 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
  48.             )
  49.         ''')
  50.         conn.commit()
  51.         conn.close()

  52.     def save_page(self, url, title, content):
  53.         # 生成文件名
  54.         parsed_url = urlparse(url)
  55.         file_name = f"{parsed_url.netloc}{parsed_url.path}".replace('/', '_')
  56.         if not file_name:
  57.             file_name = 'index'
  58.         file_path = os.path.join(self.storage_dir, file_name + '.html')
  59.         
  60.         # 保存HTML内容到文件
  61.         with open(file_path, 'w', encoding='utf-8') as f:
  62.             f.write(content)
  63.         
  64.         # 存储元数据到SQLite
  65.         conn = sqlite3.connect(self.db_path)
  66.         cursor = conn.cursor()
  67.         cursor.execute('''
  68.             INSERT OR REPLACE INTO pages (url, title, file_path)
  69.             VALUES (?, ?, ?)
  70.         ''', (url, title, file_path))
  71.         conn.commit()
  72.         conn.close()

  73. # ==================== Scheduler 层 ====================
  74. class Scheduler:
  75.     def __init__(self, max_workers=3):
  76.         self.queue = Queue()
  77.         self.visited = set()
  78.         self.crawler = Crawler()
  79.         self.parser = Parser()
  80.         self.store = Store()
  81.         self.max_workers = max_workers

  82.     def add_url(self, url):
  83.         if url not in self.visited:
  84.             self.queue.put(url)
  85.             self.visited.add(url)

  86.     def worker(self):
  87.         while True:
  88.             url = self.queue.get()
  89.             if url is None:
  90.                 break
  91.             
  92.             # 1. 抓取
  93.             html = self.crawler.fetch(url)
  94.             if not html:
  95.                 self.queue.task_done()
  96.                 continue
  97.             
  98.             # 2. 解析
  99.             parsed_data = self.parser.parse(html, url)
  100.             if parsed_data:
  101.                 # 3. 存储
  102.                 self.store.save_page(url, parsed_data['title'], html)
  103.                
  104.                 # 4. 将新链接加入队列
  105.                 for link in parsed_data['links']:
  106.                     self.add_url(link)
  107.             
  108.             self.queue.task_done()
  109.             time.sleep(1)  # 礼貌性延迟

  110.     def start(self, start_url):
  111.         self.add_url(start_url)
  112.         threads = []
  113.         for _ in range(self.max_workers):
  114.             t = Thread(target=self.worker)
  115.             t.start()
  116.             threads.append(t)
  117.         
  118.         self.queue.join()
  119.         
  120.         # 停止工作线程
  121.         for _ in range(self.max_workers):
  122.             self.queue.put(None)
  123.         for t in threads:
  124.             t.join()

  125. # ==================== 使用示例 ====================
  126. if __name__ == "__main__":
  127.     scheduler = Scheduler()
  128.     scheduler.start('[url=https://example.com')]https://example.com')[/url]
复制代码


说明:
  • Crawler 层:只负责通过 HTTP 获取原始 HTML 内容。
  • Parser 层:负责解析 HTML,提取所需的结构化数据(如标题、链接)。
  • Store 层:负责将数据存储到文件系统,并使用 SQLite 维护索引。
  • Scheduler 层:负责任务调度、URL 去重、多线程抓取以及协调其他组件。


你可以根据实际需求进一步扩展每层的功能,例如增加代理支持、异常重试机制、更复杂的数据解析等。希望这个示例能帮助你更好地组织代码!

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。
小甲鱼最新课程 -> https://ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2025-9-26 04:32

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表