1. 项目背景与目标

最近在自学Python爬虫技术,想找个有趣的项目练手。偶然发现网上有很多笑话网站,内容更新频繁且结构简单,非常适合作为爬虫练习的素材。于是决定开发一个Python爬虫程序,专门用来抓取网络上的笑话内容。

这个项目看似简单,但实际开发过程中遇到了不少有趣的问题和挑战。比如如何应对不同网站的反爬机制、如何处理动态加载内容、怎样设计合理的爬取频率等。通过这个项目,不仅能掌握基础的爬虫技术,还能学习到很多实战经验。

2. 环境准备与工具选择

2.1 Python环境配置

首先需要确保Python环境已经正确安装。推荐使用Python 3.7及以上版本,这些版本对爬虫相关库的支持更好。可以通过以下命令检查Python版本:

python --version

如果尚未安装Python,可以从官网下载安装包。安装时记得勾选"Add Python to PATH"选项,这样可以直接在命令行中使用python命令。

2.2 必备库安装

爬虫项目需要安装几个核心库:

pip install requests beautifulsoup4 lxml
  • requests:用于发送HTTP请求
  • beautifulsoup4:HTML解析库
  • lxml:解析器,比Python内置的html.parser更快更强大

如果需要处理JavaScript渲染的页面,可以额外安装selenium:

pip install selenium

此外,建议安装jupyter notebook方便调试:

pip install jupyter

3. 目标网站分析

3.1 选择合适的笑话网站

经过调研,以下几个网站适合作为爬取目标:

  1. 糗事百科:https://www.qiushibaike.com/
  2. 开心笑话:http://www.kxiao.com/
  3. 冷笑话:https://www.lengxiaohua.com/

这些网站的共同特点是:

  • 内容更新频繁
  • 页面结构简单清晰
  • 没有过于严格的反爬机制

3.2 页面结构分析

以糗事百科为例,打开开发者工具(F12)查看页面结构:

  • 笑话内容通常位于 <div class="content"> 标签内
  • 每条笑话是一个 <div class="article"> 区块
  • 分页信息在页面底部

通过这种分析,可以确定需要提取的HTML元素和属性。

4. 基础爬虫实现

4.1 获取网页内容

使用requests库获取网页HTML:

import requests

url = "https://www.qiushibaike.com/text/"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
}

response = requests.get(url, headers=headers)
html = response.text

注意添加User-Agent模拟浏览器访问,这是绕过基础反爬的重要手段。

4.2 解析HTML内容

使用BeautifulSoup解析获取的HTML:

from bs4 import BeautifulSoup

soup = BeautifulSoup(html, "lxml")
jokes = soup.find_all("div", class_="article")
for joke in jokes:
    content = joke.find("div", class_="content").get_text(strip=True)
    print(content)
    print("="*50)

这段代码会提取页面中的所有笑话内容,并用等号线分隔每条笑话。

5. 处理分页与数据存储

5.1 实现自动翻页

大多数笑话网站都采用分页显示,我们需要自动处理多页内容:

base_url = "https://www.qiushibaike.com/text/page/{}/"

for page in range(1, 6):  # 爬取前5页
    url = base_url.format(page)
    response = requests.get(url, headers=headers)
    # 解析和存储代码...

5.2 数据存储方案

爬取的数据可以保存为多种格式:

  1. 文本文件 :
with open("jokes.txt", "a", encoding="utf-8") as f:
    f.write(content + "\n\n")
  1. CSV文件 :
import csv

with open("jokes.csv", "a", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow([content])
  1. 数据库 (以SQLite为例):
import sqlite3

conn = sqlite3.connect("jokes.db")
c = conn.cursor()
c.execute("CREATE TABLE IF NOT EXISTS jokes (content TEXT)")
c.execute("INSERT INTO jokes VALUES (?)", (content,))
conn.commit()
conn.close()

6. 反爬机制应对策略

6.1 常见反爬手段

  1. User-Agent检测 :解决方案是使用常见浏览器的UA
  2. IP限制 :需要设置合理的爬取间隔或使用代理IP
  3. 验证码 :遇到验证码时需要人工干预或使用OCR识别
  4. 动态加载 :需要分析接口或使用selenium模拟浏览器

6.2 实现请求间隔

避免频繁请求被封锁:

import time
import random

time.sleep(random.uniform(1, 3))  # 随机等待1-3秒

6.3 使用代理IP

如果需要大量爬取,建议使用代理IP池:

proxies = {
    "http": "http://127.0.0.1:8888",
    "https": "http://127.0.0.1:8888"
}
response = requests.get(url, headers=headers, proxies=proxies)

7. 高级功能实现

7.1 自动分类笑话

可以使用简单的关键词匹配对笑话进行分类:

categories = {
    "职场": ["老板", "同事", "工作", "上班"],
    "校园": ["老师", "同学", "考试", "作业"],
    "家庭": ["老婆", "老公", "孩子", "父母"]
}

def classify_joke(content):
    for category, keywords in categories.items():
        if any(keyword in content for keyword in keywords):
            return category
    return "其他"

7.2 定时自动爬取

使用schedule库实现定时任务:

import schedule
import time

def job():
    print("开始爬取笑话...")
    # 爬取代码...

schedule.every().day.at("10:00").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)

8. 项目优化与扩展

8.1 异常处理增强

完善的异常处理能让爬虫更健壮:

try:
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()
except requests.exceptions.RequestException as e:
    print(f"请求失败: {e}")
    # 重试或记录日志

8.2 日志记录

添加日志功能方便调试:

import logging

logging.basicConfig(
    filename="spider.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

logging.info(f"开始爬取页面: {url}")

8.3 扩展方向

  1. 开发Web界面展示爬取的笑话
  2. 实现笑话自动推送到微信公众号
  3. 构建笑话推荐系统
  4. 开发手机APP集成笑话功能

9. 完整代码示例

以下是糗事百科笑话爬虫的完整实现:

import requests
from bs4 import BeautifulSoup
import time
import random
import logging
import sqlite3

# 配置日志
logging.basicConfig(
    filename="joke_spider.log",
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s"
)

# 数据库初始化
def init_db():
    conn = sqlite3.connect("jokes.db")
    c = conn.cursor()
    c.execute("""
        CREATE TABLE IF NOT EXISTS jokes (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            content TEXT,
            category TEXT,
            create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()
    conn.close()

# 分类函数
def classify_joke(content):
    categories = {
        "职场": ["老板", "同事", "工作", "上班"],
        "校园": ["老师", "同学", "考试", "作业"],
        "家庭": ["老婆", "老公", "孩子", "父母"]
    }
    for category, keywords in categories.items():
        if any(keyword in content for keyword in keywords):
            return category
    return "其他"

# 爬取函数
def crawl_qiushibaike(page_num=5):
    base_url = "https://www.qiushibaike.com/text/page/{}/"
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    }
    
    init_db()
    conn = sqlite3.connect("jokes.db")
    
    for page in range(1, page_num + 1):
        url = base_url.format(page)
        logging.info(f"开始爬取第{page}页: {url}")
        
        try:
            response = requests.get(url, headers=headers, timeout=10)
            response.raise_for_status()
            
            soup = BeautifulSoup(response.text, "lxml")
            jokes = soup.find_all("div", class_="article")
            
            for joke in jokes:
                content = joke.find("div", class_="content").get_text(strip=True)
                category = classify_joke(content)
                
                # 存入数据库
                conn.cursor().execute(
                    "INSERT INTO jokes (content, category) VALUES (?, ?)",
                    (content, category)
                )
                conn.commit()
                
                print(f"[{category}] {content[:50]}...")
                print("="*50)
            
            # 随机等待
            time.sleep(random.uniform(1, 3))
            
        except Exception as e:
            logging.error(f"爬取第{page}页失败: {e}")
            continue
    
    conn.close()
    logging.info("爬取任务完成")

if __name__ == "__main__":
    crawl_qiushibaike()

10. 常见问题与解决方案

10.1 爬取内容为空

可能原因:

  1. 网站结构已更新,原先的CSS选择器失效
  2. 触发了反爬机制
  3. 页面需要JavaScript渲染

解决方案:

  1. 重新分析页面结构,更新选择器
  2. 检查请求头,添加Referer等字段
  3. 使用selenium模拟浏览器

10.2 被封IP

预防措施:

  1. 设置合理的爬取间隔
  2. 使用代理IP池
  3. 遵守robots.txt规则

10.3 编码问题

处理方案:

response.encoding = response.apparent_encoding  # 自动检测编码

10.4 动态加载内容

解决方法:

  1. 分析XHR请求接口
  2. 使用selenium等待元素加载
  3. 模拟滚动等用户行为

11. 法律与道德注意事项

  1. 遵守目标网站的robots.txt协议
  2. 控制爬取频率,避免对服务器造成负担
  3. 不要爬取隐私或敏感内容
  4. 尊重版权,合理使用爬取数据
  5. 商业用途需获得授权

12. 项目总结与进阶建议

通过这个Python爬虫项目,我们实现了从笑话网站自动抓取内容的功能,并解决了爬虫开发中的常见问题。这个项目虽然简单,但涵盖了爬虫开发的核心技术点。

对于想进一步深入学习的开发者,建议:

  1. 学习Scrapy框架开发更复杂的爬虫
  2. 研究反反爬技术,如验证码识别
  3. 了解分布式爬虫架构
  4. 学习自然语言处理,对爬取内容进行分析

爬虫技术发展迅速,建议持续关注相关技术动态,同时始终牢记合法合规的原则。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐