如何构建可无限运行的网页爬虫?性能优化与技术选型咨询
无限运行网页爬虫实现方案(解决速度慢、中途停止问题)
核心逻辑修正
你原来的while(num_links_in_page > 0)逻辑有漏洞——当前页面无链接不代表整个爬取任务结束,应该维护一个待爬URL队列,只要队列不为空就持续运行。修正后的核心逻辑伪代码:
初始化待爬队列(放入起始URL) 初始化已爬URL集合(去重用) while 队列不为空: 取出一个URL 如果URL已爬过,跳过 标记为已爬 请求页面获取HTML 解析HTML提取title、meta、文章内容 将数据存入MySQL 提取页面内的有效链接(过滤目标域名、排除已爬URL) 将新链接加入队列
语言选型与实现建议
1. Python(优先推荐)
Python的爬虫生态成熟,异步库能大幅提升爬取速度,且学习成本低。用aiohttp做异步请求,BeautifulSoup解析HTML,示例代码:
import asyncio from aiohttp import ClientSession from bs4 import BeautifulSoup import pymysql # 数据库连接(按需配置) db_conn = pymysql.connect(host='localhost', user='root', password='your_pwd', db='spider_db') cursor = db_conn.cursor() visited_urls = set() queue = asyncio.Queue() # 解析HTML函数 def parse_html(html, url): soup = BeautifulSoup(html, 'html.parser') title = soup.title.string if soup.title else '' # 提取meta标签(以description为例) meta_desc = soup.find('meta', attrs={'name': 'description'}) meta_content = meta_desc['content'] if meta_desc else '' # 提取所有p标签内容 p_content = '\n'.join([p.get_text(strip=True) for p in soup.find_all('p')]) return title, meta_content, p_content, url # 批量插入数据库(优化写入速度) def batch_insert(data_list): sql = """INSERT INTO spider_data(html, title, meta, content, url) VALUES(%s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE html=VALUES(html)""" try: cursor.executemany(sql, data_list) db_conn.commit() except Exception as e: print(f"DB insert error: {str(e)}") db_conn.rollback() async def crawl(url, session, batch_data): if url in visited_urls: return visited_urls.add(url) try: # 模拟浏览器请求头,避免被拦截 headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36" } async with session.get(url, timeout=12, headers=headers) as resp: if resp.status not in [200, 201]: print(f"Skip {url} - Status: {resp.status}") return html = await resp.text() title, meta, content, url = parse_html(html, url) batch_data.append((html, title, meta, content, url)) # 提取页面内的目标域名链接(这里假设目标域名为example.com,按需修改) soup = BeautifulSoup(html, 'html.parser') for a_tag in soup.find_all('a', href=True): link = a_tag['href'] # 处理相对链接 if link.startswith('/'): link = f"https://example.com{link}" # 只保留目标域名的链接 if 'example.com' in link and link not in visited_urls: await queue.put(link) except Exception as e: print(f"Error crawling {url}: {str(e)}") async def main(initial_url): await queue.put(initial_url) batch_data = [] batch_size = 10 # 每10条数据批量插入一次 async with ClientSession() as session: while not queue.empty(): url = await queue.get() await crawl(url, session, batch_data) # 达到批量大小就插入数据库 if len(batch_data) >= batch_size: batch_insert(batch_data) batch_data = [] queue.task_done() # 插入剩余数据 if batch_data: batch_insert(batch_data) # 关闭数据库连接 cursor.close() db_conn.close() if __name__ == "__main__": asyncio.run(main("https://example.com"))
2. PHP/JavaScript方案(不换语言的选择)
- PHP:用
GuzzleHttp的异步请求实现并发,配合DOMDocument解析HTML,用Redis或者MySQL存储待爬队列和已爬URL,避免重复请求。 - JavaScript(Node.js):用
axios配合Promise.all控制并发数,用cheerio解析HTML,同样需要维护去重集合和持久化队列。
速度提升关键措施
- 异步/并发请求:不要串行爬取,控制并发数(建议5-10,根据目标网站抗压能力调整),避免单线程阻塞。
- 链接去重:用内存集合(如Python的
set)+ 数据库唯一索引双重去重,避免重复请求浪费资源。 - 请求优化:设置合理超时时间(10-15秒),只请求HTML文本(禁止下载图片、视频等静态资源),模拟浏览器请求头。
- 批量写入数据库:不要爬一条插一条,积累一定数量后批量插入,减少数据库IO开销。
解决中途停止问题
- 全局异常捕获:对每个请求、解析、数据库操作加异常捕获,记录错误日志后继续执行,避免单个请求崩溃导致整个爬虫停止。
- 队列持久化:把待爬URL存入MySQL或者本地文件,爬虫重启后可以从上次的队列继续爬取,不会前功尽弃。
- 基础反反爬:设置随机请求间隔(1-3秒),轮换User-Agent,避免被目标网站识别为爬虫封禁。
MySQL表结构参考
CREATE TABLE spider_data ( id INT AUTO_INCREMENT PRIMARY KEY, url VARCHAR(255) UNIQUE NOT NULL, -- 唯一索引去重 html LONGTEXT, title VARCHAR(255), meta TEXT, content TEXT, crawl_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
内容的提问来源于stack exchange,提问作者maybethebestprogrammer
相关产品推荐
相关产品推荐

