Python爬虫

什么是爬虫? 🕷️

基本概念

  • 定义:通过编写程序模拟浏览器上网行为,从互联网上抓取数据的过程
    • 模拟:浏览器本身就是最原始的爬虫工具
    • 抓取
      • ① 获取整张页面的源码数据
      • ② 提取页面中的局部特定数据

爬虫分类 🗂️

类型特点
通用爬虫爬取整个页面源码(基础型)
聚焦爬虫提取页面局部数据(需先完成通用爬虫)✨
增量式爬虫监测网站数据更新,抓取最新内容
分布式爬虫多节点协同工作,大幅提升效率(终极武器)🚀

反爬与反反爬 ⚔️

  • 反爬机制
    网站通过技术手段阻止爬虫获取数据(如验证码、IP限制等)

  • 反反爬策略
    爬虫程序通过伪装头部、代理IP等技术突破反爬限制

第一种反爬策略

  • robots.txt协议 📜
  • 网站通过robots.txt声明哪些内容允许/禁止爬取
  • 示例:访问 淘宝robots协议
  • ⚠️ 注意:该协议仅具道德约束力,无强制技术限制(防君子不防小人)

学习阶段可暂时忽略robots协议,但商业项目需严格遵守哦~

爬虫的合法与非法边界 ⚖️

技术中立原则 🖥️

“爬虫技术本身无罪,互联网上50%以上的流量由爬虫创造,但错误使用可能构成犯罪”
——《中华人民共和国网络安全法》实施后,灰色地带业务已被明确规范

非法爬虫行为 🚨

⚠️ 触及刑法的三种情形

  1. 非法获取计算机信息系统数据罪

    • 规避反爬措施/破解防抓取技术获取信息
  2. 破坏计算机信息系统罪

    • 导致目标网站/系统无法正常运营
  3. 侵犯公民个人信息罪

    • 采集包含身份证号、住址等敏感信息

合法爬虫准则 ✅

📜 三大核心原则

  1. 遵守Robots协议

    • 严格遵循robots.txt声明(如淘宝允许爬取商品页但禁止用户评论)
  2. 控制访问频率

    • 单日请求量不超过网站流量1/3
    • 收到停止警告立即终止
  3. 禁止数据牟利

    • 不得复制商用(如克隆大众点评)
    • 不得转售付费内容(如盗版课程)

风险警示牌 🔍

风险类型典型案例
技术风险高频访问导致服务器瘫痪
法律风险爬取医疗/金融等受保护数据

程序员防踩坑指南 🛡️

  1. 技术规范

    • 设置User-Agent标识身份
    • 添加Delay(3-5秒)降低访问频率
  2. 数据过滤

    • 自动屏蔽身份证/手机号等字段
    • 建立敏感词库实时检测
  3. 法律自查

    • 定期核对《网络安全法》更新条款
    • 商业项目需法律顾问审核

💡 黄金法则:当不确定是否合法时,想象自己的数据被爬取是否会愤怒

📝 Requests 爬虫基础笔记

🔍 Request基础模块

  • 爬虫中基于网络请求的核心模块
  • 安装命令:pip install requests
  • 作用:模拟浏览器发起HTTP请求
  • 编码流程:
    1. 指定目标URL 🎯
    2. 发起网络请求
    3. 获取响应数据
    4. 持久化存储 💾

搜狗页面源码爬取示例

import requests

# 1. 指定URL
url = "https://www.sogou.com"

# 2. 发起请求(返回响应对象)
response = requests.get(url)

# 3. 获取响应数据(页面源码)
page_text = response.text  # 返回字符串格式数据

# 4. 持久化存储
with open('./sogou.html', 'w', encoding='utf-8') as fp:
    fp.write(page_text)

Requests 模块详解 🕷️

Python 网络爬虫的基石,轻松实现数据采集与自动化请求

🧩 Requests 基础模块

  • 功能:基于网络请求的爬虫核心模块
  • 安装pip install requests
  • 作用:模拟浏览器发起 HTTP 请求
  • 核心流程
    1. 指定目标 URL
    2. 发起 HTTP 请求
    3. 获取响应数据(页面源码)
    4. 持久化存储

爬取搜狗首页示例

import requests

# 1. 指定目标URL
url = "https://www.sogou.com"

# 2. 发起GET请求(返回响应对象)
response = requests.get(url)

# 3. 获取响应数据(文本格式)
page_text = response.text

# 4. 持久化存储
with open('./sogou.html', 'w', encoding='utf-8') as fp:
    fp.write(page_text)

🔍 简易网页采集器

基础实现(带动态参数)

import requests

keyword = input("请输入需要爬取的关键字:")

# 动态参数处理
params = {
    "query": keyword  # 将关键字封装为查询参数
}

url = "https://www.sogou.com/web"

# 发送带参数的GET请求
response = requests.get(url=url, params=params)

page_text = response.text
fileName = keyword + ".html"

with open(fileName, 'w', encoding='utf-8') as fp:
    fp.write(page_text)

print("爬取成功!")

🚨 常见问题解决方案

  1. 乱码问题

    # 修改响应数据的编码格式
    response.encoding = "utf-8"  # 强制使用UTF-8编码
    page_text = response.text
    
  2. 数据丢失问题(反爬机制)

    • 原因:网站通过检测User-Agent识别爬虫程序
    • 解决方案:UA伪装(模拟浏览器请求头)
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
    }
    
    response = requests.get(url=url, params=params, headers=headers)
    

完整解决方案

import requests

keyword = input("请输入需要爬取的关键字:")

# UA伪装
headers = {
    "User-Agent": "News (+http://www.yourdomain.com)"
}

# 动态参数
params = {"query": keyword}
url = "https://www.sogou.com/web"

# 发送请求
response = requests.get(url=url, params=params, headers=headers)
response.encoding = "utf-8"  # 解决乱码

page_text = response.text
fileName = keyword + ".html"

with open(fileName, 'w', encoding='utf-8') as fp:
    fp.write(page_text)

print(f"✅ '{keyword}'爬取成功!")

🌐 动态加载数据处理

识别动态数据

  1. 使用抓包工具(如Chrome开发者工具)
  2. 在Response选项卡搜索目标数据
  3. 若未找到→数据为动态加载
    在这里插入图片描述

豆瓣电影数据爬取示例

import requests

headers = {
    "User-Agent": "News (+http://www.yourdomain.com)"
}

# 动态数据接口
url = "https://movie.douban.com/j/chart/top_list"

# 接口参数
params = {
    "type": "24",          # 喜剧类型
    "interval_id": "100:90",
    "action": "",
    "start": "0",          # 起始位置
    "limit": "20",         # 每页数量
}

response = requests.get(url, params=params, headers=headers)

# 处理JSON响应
movie_data = response.json()

for movie in movie_data:
    name = movie["title"]
    score = movie["score"]
    print(f"🎬 电影:{name} | 评分:{score}")

print("豆瓣数据爬取完成!")

📖 分页数据爬取

肯德基餐厅位置爬取

import requests

headers = {
    "User-Agent": "News (+http://www.yourdomain.com)"
}

for page in range(1, 9):  # 爬取前8页
    # POST请求参数
    data = {
        'cname': '北京',
        'pid': '',
        'keyword': '北京',
        'pageIndex': str(page),  # 当前页码
        'pageSize': '10',        # 每页数量
    }

    url = 'https://www.kfc.com.cn/kfccda/ashx/GetStoreList.ashx?op=keyword'
    
    # POST请求(注意使用data参数)
    response = requests.post(url=url, data=data, headers=headers)
    
    # 处理JSON响应
    store_data = response.json()
    
    for store in store_data['Table1']:
        name = store['storeName']
        address = store['addressDetail']
        print(f"🏪 第{page}页 | 店名:{name} | 地址:{address}")

print("✅ 肯德基门店数据爬取完成!")

💡 核心技巧总结

  1. 动态参数处理:使用params字典封装GET参数
  2. POST请求:使用data参数传递表单数据
  3. UA伪装:设置headers模拟浏览器
  4. 编码处理response.encoding = 'utf-8'
  5. JSON响应response.json()直接解析
  6. 分页爬取:循环修改页码参数
  7. 异常处理:建议添加try-except

提示:实际项目中应添加请求间隔错误重试机制,避免被封IP!🛡️

# 推荐的安全请求模板
import requests
import time
from random import uniform

headers = {
    "User-Agent": "News (+http://www.yourdomain.com)",
    "Accept-Language": "zh-CN,zh;q=0.9"
}

def safe_request(url, params=None, method='GET', retry=3):
    for _ in range(retry):
        try:
            if method == 'GET':
                response = requests.get(url, params=params, headers=headers, timeout=10)
            else:
                response = requests.post(url, data=params, headers=headers, timeout=10)
            
            response.encoding = 'utf-8'
            return response
        
        except Exception as e:
            print(f"请求失败: {e}, 重试中...")
            time.sleep(uniform(1, 3))  # 随机等待1-3秒
    
    return None  # 所有重试失败

掌握这些技巧,你就能应对90%的网页数据采集需求!🚀

数据解析技术全解析 📊

聚焦爬虫的核心技术,精准提取目标数据

🔍 数据解析的作用与原理

  • 作用:实现聚焦爬虫的核心技术,从网页中精准提取目标数据
  • 数据存储位置
    • HTML标签内容中
    • 标签属性值中
    • JavaScript脚本中
  • 通用原理
    1. 标签定位:找到存储目标数据的标签
    2. 数据提取:取出标签内容或属性值
网页源码
解析方式
正则表达式
BeautifulSoup
XPath
数据提取
结构化数据

🔤 正则表达式解析

常用正则表达式语法

类别表达式说明示例
单字符.除换行外任意字符a.c 匹配 “abc”、“aac”
[aoe]匹配集合中任意字符[ae]pple 匹配 “apple” 或 “epple”
\d数字 [0-9]\d{3} 匹配 “123”、“456”
\w字母/数字/下划线/中文\w+ 匹配 “hello”、“user123”
\s空白字符hello\sworld 匹配 “hello world”
数量修饰*0次或多次a*b 匹配 “b”、“ab”、“aab”
+1次或多次a+b 匹配 “ab”、“aab”
?0次或1次colou?r 匹配 “color” 或 “colour”
{m}固定m次a{3} 匹配 “aaa”
{m,n}m到n次a{2,4} 匹配 “aa”、“aaa”、“aaaa”
边界^字符串开头^Hello 匹配以 “Hello” 开头的字符串
$字符串结尾world$ 匹配以 “world” 结尾的字符串
分组(ab)捕获分组(ab)+ 匹配 “ab”、“abab”
模式.*贪婪模式<div>.*</div> 匹配整个div内容
.*?非贪婪模式<div>.*?</div> 匹配最小div内容
标志re.I忽略大小写匹配 “hello”、“HELLO”
re.S使.匹配所有字符匹配含换行的内容
re.M多行匹配多行文本中匹配每行开头

Python re模块使用示例

import re

# 示例1:匹配手机号码
text = "联系方式:13800138000,备用:13900139000"
phone_pattern = r'1[3-9]\d{9}'
phones = re.findall(phone_pattern, text)
print("手机号码:", phones)  # ['13800138000', '13900139000']

# 示例2:提取电子邮件
text = "联系邮箱:contact@example.com,support@domain.com"
email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'
emails = re.findall(email_pattern, text, re.IGNORECASE)
print("电子邮箱:", emails)  # ['contact@example.com', 'support@domain.com']

# 示例3:解析HTML标签
html = '<div class="content"><p>第一段</p><p>第二段</p></div>'
pattern = r'<p>(.*?)</p>'
paragraphs = re.findall(pattern, html, re.S)
print("段落内容:", paragraphs)  # ['第一段', '第二段']

# 示例4:替换文本
text = "今天是2023-08-15,明天是2023-08-16"
new_text = re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\1年\2月\3日', text)
print("格式化日期:", new_text)  # 今天是2023年08月15日,明天是2023年08月16日

# 示例5:分组提取
text = "姓名:张三,年龄:25;姓名:李四,年龄:30"
pattern = r'姓名:(\w+),年龄:(\d+)'
matches = re.findall(pattern, text)
for name, age in matches:
    print(f"{name} - {age}岁")  # 张三 - 25岁, 李四 - 30岁

图片爬取方法对比

方法1:基于requests
import requests

headers = {"User-Agent": "Mozilla/5.0..."}
img_url = "http://example.com/image.jpg"

response = requests.get(img_url, headers=headers)
img_data = response.content  # 获取二进制数据

with open("image.jpg", "wb") as f:
    f.write(img_data)
方法2:基于urllib
import urllib.request

img_url = "http://example.com/image.jpg"
urllib.request.urlretrieve(img_url, "./image.jpg")

⚠️ 关键区别
requests支持UA伪装,urllib无法直接进行UA伪装,安全性较低

正则批量爬取图片

import requests
import re
import os

url = 'https://www.qiushibaike.com/pic/%s/'
headers = {'User-Agent': 'Mozilla/5.0...'}

# 创建存储目录
if not os.path.exists('images'):
    os.mkdir('images')

for page in range(1, 6):  # 爬取5页
    page_url = url % page
    response = requests.get(page_url, headers=headers)
    
    # 解析图片URL
    pattern = re.compile('<div class="thumb">.*?<img src="(.*?)".*?>.*?</div>', re.S)
    image_urls = pattern.findall(response.text)
    
    for img_path in image_urls:
        img_url = 'https:' + img_path
        img_name = img_url.split('/')[-1]
        
        # 下载并保存图片
        img_data = requests.get(img_url, headers=headers).content
        with open(f'images/{img_name}', 'wb') as f:
            f.write(img_data)
        print(f'✅ 已下载: {img_name}')

🧩 Elements vs Network 分析

浏览器开发者工具关键区别

  1. Elements选项卡

    • 显示当前页面所有数据加载完毕后对应的完整页面源码
    • 包含动态加载数据和JavaScript渲染后的内容
    • 适合分析静态页面结构
  2. Network选项卡

    • 显示单个请求对应的响应数据
    • 不包含动态加载的数据(仅原始响应)
    • 适合分析API接口和动态数据加载

结论

  • 如果网站没有动态加载数据 → 使用Elements分析页面布局
  • 如果网站有动态加载数据 → 使用Network分析数据请求

🥣 BeautifulSoup解析

解析原理

  1. 实例化BeautifulSoup对象
  2. 加载页面源码数据
  3. 调用对象方法定位标签
  4. 提取文本或属性数据

环境安装

pip install lxml  # 高性能解析器
pip install bs4   # BeautifulSoup库

对象实例化

from bs4 import BeautifulSoup

# 本地HTML文档解析
with open("local.html") as fp:
    soup = BeautifulSoup(fp, "lxml")

# 网络请求数据解析
response = requests.get(url)
soup = BeautifulSoup(response.text, "lxml")

标签定位方法

方法示例说明
直接定位soup.a第一个a标签
find()soup.find('div')第一个div标签
soup.find(class_="content")按class定位
soup.find(id="main")按id定位
find_all()soup.find_all('p')所有p标签
soup.find_all(['p', 'div'])多标签类型
soup.find_all(limit=3)限制数量
CSS选择器soup.select('.class')类选择器
soup.select('#id')ID选择器
soup.select('div > p')子元素选择
soup.select('div p')后代元素选择

数据提取

# 获取文本内容
tag.text     # 所有子标签文本
tag.string   # 仅直系文本

# 获取属性值
tag['href']  # href属性
tag.get('src') # 安全获取属性

实战:爬取《三国演义》

import requests
from bs4 import BeautifulSoup
import os

base_url = "https://www.shicimingju.com"
main_url = base_url + "/book/sanguoyanyi.html"
headers = {"User-Agent": "Mozilla/5.0..."}

# 创建存储目录
if not os.path.exists("三国演义"):
    os.mkdir("三国演义")

# 获取目录页
response = requests.get(main_url, headers=headers)
soup = BeautifulSoup(response.content, "lxml")

# 解析章节链接
chapters = soup.select('div.book-mulu > ul > li > a')
for chapter in chapters:
    title = chapter.text.strip()
    chapter_url = base_url + chapter['href']
    
    # 获取章节内容
    chap_res = requests.get(chapter_url, headers=headers)
    chap_soup = BeautifulSoup(chap_res.content, "lxml")
    content = chap_soup.select_one('div.chapter_content').text
    
    # 保存内容
    with open(f"三国演义/{title}.txt", "w", encoding="utf-8") as f:
        f.write(content)
    print(f"📖 {title} 保存成功!")

🔭 XPath解析

环境安装

pip install lxml

解析原理

  1. 实例化etree对象
  2. 加载页面源码数据
  3. 使用XPath表达式定位
  4. 提取文本或属性值

对象实例化

from lxml import etree

# 本地文件解析
tree = etree.parse('local.html')

# 网络数据解析
tree = etree.HTML(response.text)

XPath定位技巧

表达式示例说明
路径定位/html/body/div绝对路径
//div文档中所有div
属性定位//div[@id="main"]按id定位
//a[@class="external"]按class定位
索引定位(//div)[3]第3个div元素
模糊匹配//div[contains(@class, "ng")]包含特定class
//div[starts-with(@id, "sec")]id以sec开头
逻辑运算//div[@class="a" or @class="b"]或条件
//div[@class="a" and @id="b"]与条件
轴定位//div/child::p子元素p
//div/descendant::span后代span
//div/following-sibling::*后续兄弟节点

数据提取

# 获取文本
element.xpath('./text()')       # 直接文本
element.xpath('.//text()')      # 所有子节点文本

# 获取属性
element.xpath('./@href')        # href属性
element.xpath('img/@src')       # img的src属性

实战:批量爬取壁纸

from lxml import etree
import requests
import os

base_url = "https://pic.netbian.com"
headers = {"User-Agent": "Mozilla/5.0..."}
save_dir = "4K壁纸"

if not os.path.exists(save_dir):
    os.mkdir(save_dir)

for page in range(1, 6):  # 爬取5页
    page_url = f"{base_url}/4kfengjing/index_{page}.html" if page > 1 else f"{base_url}/4kfengjing/"
    
    response = requests.get(page_url, headers=headers)
    response.encoding = 'gbk'  # 处理编码问题
    tree = etree.HTML(response.text)
    
    # 定位图片元素
    img_elements = tree.xpath('//ul[@class="clearfix"]/li/a')
    
    for elem in img_elements:
        title = elem.xpath('./b/text()')[0]
        img_src = base_url + elem.xpath('./img/@src')[0]
        
        # 下载图片
        img_data = requests.get(img_src, headers=headers).content
        with open(f"{save_dir}/{title}.jpg", "wb") as f:
            f.write(img_data)
        print(f"🖼️ 已下载: {title}")

局部数据解析技巧

# 全局解析
main_elements = tree.xpath('//div[@class="container"]')

for element in main_elements:
    # 局部解析 - 注意使用./
    title = element.xpath('./h2/text()')[0]
    content = element.xpath('.//p/text()')
    
    print(f"标题: {title}, 内容: {content}")

🛡️ 高级反爬策略应对

1. 图片懒加载

  • 现象:图片仅在进入可视区域时才加载
  • 原理:使用data-src等伪属性代替src
  • 解决方案:解析伪属性而非真实属性
# 正确解析懒加载图片
img_elements = tree.xpath('//img[@data-src]')
for img in img_elements:
    real_src = img.xpath('./@data-src')[0]  # 获取真实URL
    # 下载图片...

2. 动态数据加载

  • 识别方法
    1. 查看Network中的XHR请求
    2. 搜索数据是否在初始HTML中
  • 解决方案:直接请求数据接口

3. 请求频率限制

  • 应对策略
    import time
    import random
    
    # 随机延时
    time.sleep(random.uniform(1, 3))
    
    # 使用代理IP池
    proxies = {
        'http': 'http://user:pass@10.10.1.10:3128',
        'https': 'http://10.10.1.10:1080',
    }
    response = requests.get(url, proxies=proxies)
    

🧪 数据解析最佳实践

  1. 多解析方式结合:复杂页面混合使用XPath和CSS选择器
  2. 数据清洗:使用strip()等方法清理空白字符
    clean_text = [text.strip() for text in dirty_text if text.strip()]
    
  3. 异常处理:预防元素不存在情况
    try:
        title = element.xpath('./h2/text()')[0]
    except IndexError:
        title = "未知标题"
    
  4. 编码处理:动态检测页面编码
    import chardet
    
    encoding = chardet.detect(response.content)['encoding']
    response.encoding = encoding
    
  5. 增量爬取:记录已爬取URL避免重复
    if url not in crawled_urls:
        # 爬取逻辑
        crawled_urls.add(url)
    

🚀 高级技巧扩展

1. XPath轴应用

# 获取父节点
parent = element.xpath('..')

# 获取后续所有兄弟节点
following_siblings = element.xpath('following-sibling::*')

# 获取前面所有兄弟节点
preceding_siblings = element.xpath('preceding-sibling::*')

2. BS4对象类型

from bs4 import BeautifulSoup, Tag, NavigableString

soup = BeautifulSoup(html, 'lxml')
for element in soup.descendants:
    if isinstance(element, Tag):
        print("标签:", element.name)
    elif isinstance(element, NavigableString):
        print("文本:", element.strip())

3. 正则表达式优化

import re
from functools import lru_cache

# 缓存编译后的正则表达式
@lru_cache(maxsize=128)
def compile_regex(pattern):
    return re.compile(pattern)

# 使用
pattern = compile_regex(r'\d{4}-\d{2}-\d{2}')
dates = pattern.findall(text)

掌握这些数据解析技术,你将能高效地从各种网页中提取结构化数据!💪 在实际项目中,建议根据目标网站的复杂程度选择最适合的解析方式,并始终记得遵守网站的robots.txt规定。

高级请求技术详解 🚀

突破反爬限制,实现高效数据采集

🍪 Cookie处理机制

Cookie基础概念

  • 定义:存储在客户端的一组键值对,用于维持会话状态
  • 常见用途:用户身份认证、免密登录、个性化设置
  • 与爬虫的关联
    • 部分网站要求请求必须携带有效Cookie才能获取正确数据
    • Cookie是常见的反爬机制之一,用于识别异常请求

雪球网爬取实战

import requests
from lxml import etree

# 创建会话对象(自动处理Cookie)
session = requests.Session()

# 第一次请求:获取并存储Cookie
home_url = "https://xueqiu.com/"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
}
session.get(home_url, headers=headers)  # 此请求会捕获并存储Cookie

# 第二次请求:携带Cookie访问API
api_url = "https://xueqiu.com/statuses/hot/listV3.json?page=3&last_id=341337429&md5__1038=2840ce7d37-rYX55dydysSRzdKXdiwQXdOQusukzzR138dAAQaucuyIBV/04TsmslyR0qhw_u=uyZ4uTp0ilmuPziPujzioyuu3ud0aQuM0abu1NuXXTucziBjgzXcuRUSFucn3punuifuz0ufuS0aOnjTzu8uSeuPKfqyncY/ybYuVZXchbfPScejS4EuuwlqXfYXhuu"
response = session.get(api_url, headers=headers)

# 解析JSON数据
data = response.json()
for item in data.get("items", []):
    title = item.get("title")
    description = item.get("description")
    print(f"📰 标题: {title}")
    print(f"📝 内容: {description[:50]}...")
    print("-" * 50)

Cookie处理策略对比

处理方式实现方法优点缺点适用场景
手动处理从浏览器复制Cookie到headers简单直接Cookie过期需重新获取;无法处理动态Cookie短期小规模爬取
自动处理使用requests.Session()对象自动管理Cookie生命周期;支持多次请求需要至少两次请求(首次获取Cookie)长期大规模爬取

Session对象关键点

  1. session = requests.Session() 创建会话对象
  2. 首次请求:获取并存储Cookie
  3. 后续请求:自动携带Cookie
  4. 会话保持:同一Session对象维持Cookie状态

动态Cookie处理技巧

# 处理动态生成的Cookie
def handle_dynamic_cookies():
    session = requests.Session()
    
    # 第一步:获取初始Cookie
    init_response = session.get("https://example.com/login")
    
    # 第二步:解析动态Token(常见反爬措施)
    soup = BeautifulSoup(init_response.text, 'lxml')
    csrf_token = soup.find('input', {'name': 'csrf_token'})['value']
    
    # 第三步:携带Token登录
    login_data = {
        'username': 'your_username',
        'password': 'your_password',
        'csrf_token': csrf_token
    }
    session.post("https://example.com/login", data=login_data)
    
    # 第四步:使用已认证的Session访问数据
    data_response = session.get("https://example.com/protected-data")
    return data_response.json()

🔄 代理机制详解

代理基础概念

  • 代理服务器作用:转发客户端请求和服务器响应
  • 爬虫中使用代理的原因
    • 防止IP被封锁(高频请求触发反爬)
    • 访问地域限制内容
    • 隐藏真实爬虫身份

代理分类

分类标准类型说明
代理方向正向代理代理客户端,保护客户端身份
反向代理代理服务器,用于负载均衡和安全防护
匿名度透明代理服务器知道真实IP和代理使用
匿名代理服务器知道使用代理但不知道真实IP
高匿代理服务器无法检测代理使用和真实IP
协议HTTP代理仅支持HTTP协议
HTTPS代理支持HTTPS加密协议
SOCKS代理支持多种协议,更通用

代理使用实战

import requests
import random

# 用户代理列表
header_list = [
    {"User-Agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Maxthon 2.0)"},
    {"User-Agent": "Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1"},
    {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_0) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11"}
]

# 代理IP列表(实际使用时应从专业代理服务获取)
proxy_list = [
    {"http": "112.115.57.20:3128", "https": "112.115.57.20:3128"},
    {"http": "121.41.171.223:3128", "https": "121.41.171.223:3128"},
    {"http": "183.89.115.142:8080", "https": "183.89.115.142:8080"}
]

def rotate_proxy(url, max_retries=3):
    """使用随机代理访问URL"""
    for attempt in range(max_retries):
        try:
            # 随机选择UA和代理
            headers = random.choice(header_list)
            proxy = random.choice(proxy_list)
            
            print(f"🔁 尝试 #{attempt+1}: 使用代理 {list(proxy.values())[0]}")
            
            # 发送请求
            response = requests.get(url, 
                                   headers=headers, 
                                   proxies=proxy,
                                   timeout=10)
            
            # 检查响应状态
            if response.status_code == 200:
                print("✅ 请求成功!")
                return response
                
        except Exception as e:
            print(f"❌ 请求失败: {str(e)}")
    
    print("🚫 所有尝试均失败")
    return None

# 测试代理
url = "http://httpbin.org/ip"  # 显示当前IP的测试服务
response = rotate_proxy(url)

if response:
    print("当前IP信息:", response.json())
    
    # 保存响应内容
    with open('proxy_test.html', 'wb') as f:
        f.write(response.content)

代理管理最佳实践

  1. 代理池维护

    class ProxyPool:
        def __init__(self):
            self.proxies = []  # 初始代理列表
            self.valid_proxies = []  # 已验证的有效代理
            self.blacklist = []  # 失效代理黑名单
        
        def add_proxy(self, proxy):
            if proxy not in self.blacklist:
                self.proxies.append(proxy)
        
        def verify_proxy(self, proxy, test_url="http://httpbin.org/ip", timeout=5):
            try:
                response = requests.get(test_url, proxies=proxy, timeout=timeout)
                if response.status_code == 200:
                    print(f"✅ 代理可用: {proxy}")
                    return True
            except:
                pass
            print(f"❌ 代理不可用: {proxy}")
            return False
        
        def get_random_proxy(self):
            if not self.valid_proxies:
                self.refresh_pool()
            return random.choice(self.valid_proxies)
        
        def refresh_pool(self):
            self.valid_proxies = [p for p in self.proxies if self.verify_proxy(p)]
            print(f"🔄 代理池刷新: {len(self.valid_proxies)}个可用代理")
    
  2. 智能代理切换

    def smart_proxy_request(url, proxy_pool, max_attempts=5):
        for _ in range(max_attempts):
            proxy = proxy_pool.get_random_proxy()
            try:
                response = requests.get(url, proxies=proxy, timeout=15)
                if response.status_code == 200:
                    return response
                elif response.status_code == 403:
                    print("⛔ IP被禁止,切换到下一个代理")
                    proxy_pool.blacklist.append(proxy)
            except requests.exceptions.RequestException as e:
                print(f"代理错误: {e}")
        return None
    
  3. 清除代理设置

    # 恢复直连(不使用代理)
    response = requests.get(url, proxies={"http": None, "https": None})
    

🛡️ 高级反爬对策

综合防护策略

  1. 用户代理轮换

    from fake_useragent import UserAgent
    
    ua = UserAgent()
    headers = {'User-Agent': ua.random}
    
  2. 请求频率控制

    import time
    import random
    
    # 随机延时(1-3秒)
    time.sleep(random.uniform(1, 3))
    
    # 更智能的请求间隔
    def intelligent_delay(last_request_time, min_delay=1, max_delay=5):
        current_time = time.time()
        elapsed = current_time - last_request_time
        
        if elapsed < min_delay:
            sleep_time = min_delay - elapsed + random.uniform(0, 1)
            time.sleep(sleep_time)
        elif elapsed > max_delay:
            time.sleep(random.uniform(0.5, 1.5))
        return time.time()
    
  3. 请求头完整性

    headers = {
        'User-Agent': 'Mozilla/5.0...',
        'Accept': 'text/html,application/xhtml+xml...',
        'Accept-Language': 'en-US,en;q=0.9',
        'Connection': 'keep-alive',
        'Referer': 'https://www.google.com/',
        'Upgrade-Insecure-Requests': '1'
    }
    
  4. HTTPS证书处理

    # 忽略SSL证书验证(谨慎使用)
    response = requests.get(url, verify=False)
    
    # 使用自定义证书
    response = requests.get(url, cert=('/path/client.cert', '/path/client.key'))
    

📊 高级技巧:请求监控与分析

使用请求钩子

def print_request_info(request):
    print(f"🚀 请求发出: {request.method} {request.url}")
    print(f"📦 请求头: {request.headers}")
    return request

def print_response_info(response, *args, **kwargs):
    print(f"✅ 收到响应: {response.status_code}")
    print(f"⏱️ 请求耗时: {response.elapsed.total_seconds():.2f}s")
    return response

# 注册钩子函数
session = requests.Session()
session.hooks['request'] = [print_request_info]
session.hooks['response'] = [print_response_info]

# 发送请求(自动触发钩子)
session.get("https://api.example.com/data")

性能优化技巧

# 使用连接池
adapter = requests.adapters.HTTPAdapter(
    pool_connections=100,  # 连接池数量
    pool_maxsize=100,      # 最大连接数
    max_retries=3         # 最大重试次数
)
session.mount('http://', adapter)
session.mount('https://', adapter)

# 启用HTTP/2
import httpx
client = httpx.Client(http2=True)
response = client.get('https://example.com')

💎 总结

  1. Cookie处理

    • 使用Session对象自动管理Cookie生命周期
    • 对于复杂场景,手动处理动态Cookie和Token
  2. 代理机制

    • 优先选择高匿代理
    • 实现代理池轮换和验证
    • 结合UA轮换增强匿名性
  3. 高级反爬对策

    • 模拟人类行为模式(随机延时、完整请求头)
    • 监控请求性能,及时调整策略
    • 遵守目标网站robots.txt规定
  4. 性能优化

    • 使用连接池减少TCP握手开销
    • 支持HTTP/2提升传输效率
    • 异步请求处理提高吞吐量

提示:在实际项目中,建议使用专业代理服务(如Luminati、Smartproxy)和成熟的爬虫框架(如Scrapy)以获得更好的稳定性和可维护性。

Selenium 全面指南 🚗

浏览器自动化利器,实现所见即所得的爬虫操作

🧩 Selenium 基础概念

  • 定义:基于浏览器自动化的模块
  • 核心功能
    • 通过代码模拟用户在浏览器的操作行为
    • 支持多种浏览器(Chrome, Firefox, Edge, Safari等)
  • 安装
    pip install selenium
    
  • 与爬虫的关联
    1. 捕获动态加载数据:直接获取完整渲染后的页面内容(可见即可得)
    2. 实现复杂交互:处理登录、表单提交、点击等用户操作
    3. 绕过JS反爬:解决纯请求库难以处理的JavaScript渲染内容

🚀 快速入门示例

from selenium import webdriver
from selenium.webdriver.edge.service import Service
from selenium.webdriver.common.by import By
from time import sleep

# 1. 创建浏览器驱动服务
service = Service(executable_path="./EdgeDriver/msedgedriver.exe")

# 2. 实例化浏览器对象
bro = webdriver.Edge(service=service)

# 3. 打开目标网站
bro.get("https://www.jd.com")
sleep(4)  # 等待页面加载

# 4. 定位搜索框并输入关键词
search_text = bro.find_element(By.XPATH, "//*[@id='key']")
search_text.send_keys("手机")  # 输入"手机"

# 5. 定位搜索按钮并点击
btn = bro.find_element(By.XPATH, "//*[@id='search']/div[2]/div[2]/button")
btn.click()
sleep(10)  # 查看结果

# 6. 关闭浏览器
bro.close()

代码解析:

  1. 驱动配置:需要下载与浏览器版本匹配的驱动(如EdgeDriver)
  2. 元素定位:使用find_element方法配合定位策略(如XPATH)
  3. 操作元素
    • send_keys():模拟键盘输入
    • click():模拟鼠标点击
  4. 等待机制:使用time.sleep()简单等待,实际项目中应使用智能等待

⚠️ Selenium 的局限性

  • 执行效率低:相比直接HTTP请求,浏览器自动化操作较慢
  • 资源消耗大:每个浏览器实例占用较多内存和CPU资源
  • 易被检测:高级网站可能检测自动化浏览器特征

🕸️ 动作链(ActionChains)

概念

  • 定义:用于执行连续、复杂的用户交互操作
  • 适用场景
    • 鼠标悬停
    • 拖拽操作
    • 右键菜单
    • 组合键操作

基本使用流程

from selenium.webdriver import ActionChains

# 1. 实例化动作链对象
action = ActionChains(bro)

# 2. 定义动作序列
action.click_and_hold(element)  # 点击并按住元素
action.move_by_offset(100, 0)   # 水平移动100像素
action.release()                # 释放鼠标

# 3. 执行动作链
action.perform()

常见动作链方法

方法描述示例
click()单击元素action.click(element)
double_click()双击元素action.double_click(element)
context_click()右键点击action.context_click(element)
drag_and_drop()拖拽元素action.drag_and_drop(source, target)
move_to_element()鼠标悬停action.move_to_element(element)
send_keys()按键操作action.send_keys(Keys.ARROW_DOWN)
key_down()按下修饰键action.key_down(Keys.CONTROL)
key_up()释放修饰键action.key_up(Keys.CONTROL)

实战:模拟拖拽操作

from selenium import webdriver
from selenium.webdriver import ActionChains
from time import sleep

service = Service(executable_path="./EdgeDriver/msedgedriver.exe")
bro = webdriver.Edge(service=service)
bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')

# 定位拖拽元素和目标元素
drag = bro.find_element(By.ID, 'draggable')
drop = bro.find_element(By.ID, 'droppable')

# 创建动作链
action = ActionChains(bro)
action.drag_and_drop(drag, drop)  # 定义拖拽动作

# 执行动作
action.perform()
sleep(3)
bro.quit()

🎯 元素定位高级技巧

八大定位策略

from selenium.webdriver.common.by import By

# 1. ID定位
element = bro.find_element(By.ID, "username")

# 2. 名称定位
element = bro.find_element(By.NAME, "password")

# 3. 类名定位
element = bro.find_element(By.CLASS_NAME, "form-control")

# 4. 标签名定位
element = bro.find_element(By.TAG_NAME, "input")

# 5. 链接文本定位(完全匹配)
element = bro.find_element(By.LINK_TEXT, "忘记密码?")

# 6. 部分链接文本定位
element = bro.find_element(By.PARTIAL_LINK_TEXT, "忘记")

# 7. CSS选择器定位
element = bro.find_element(By.CSS_SELECTOR, "input.btn-primary")

# 8. XPath定位
element = bro.find_element(By.XPATH, "//input[@id='search']")

智能等待机制

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# 显式等待(最多10秒,直到元素可见)
element = WebDriverWait(bro, 10).until(
    EC.visibility_of_element_located((By.ID, "dynamicElement"))
)

# 常用等待条件:
# EC.presence_of_element_located   元素存在
# EC.element_to_be_clickable      元素可点击
# EC.text_to_be_present_in_element 元素包含特定文本

🛡️ 规避检测技巧

常见反自动化检测

  1. WebDriver属性检测
  2. 浏览器指纹识别
  3. 行为模式分析

应对策略

# 1. 修改WebDriver属性
bro.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
    Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined
    })
    """
})

# 2. 使用无头模式并添加参数
options = webdriver.EdgeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_argument("user-agent=Mozilla/5.0...")
options.add_argument("--start-maximized")
options.add_argument("--headless")  # 无头模式

# 3. 随机化操作模式
import random
import time

def human_type(element, text):
    """模拟人类输入速度"""
    for char in text:
        element.send_keys(char)
        time.sleep(random.uniform(0.05, 0.2))

📊 实战:爬取动态加载数据

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import time
import csv

options = webdriver.EdgeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")

service = Service(executable_path="./EdgeDriver/msedgedriver.exe")
bro = webdriver.Edge(service=service, options=options)

bro.get('https://www.taobao.com')

try:
    # 等待搜索框加载
    search_box = WebDriverWait(bro, 10).until(
        EC.presence_of_element_located((By.ID, "q"))
    )
    
    search_box.send_keys("笔记本电脑")
    
    # 点击搜索按钮
    bro.find_element(By.CSS_SELECTOR, "button.btn-search").click()
    
    # 等待结果加载
    WebDriverWait(bro, 10).until(
        EC.presence_of_element_located((By.CSS_SELECTOR, ".item.J_MouserOnverReq"))
    )
    
    # 滚动加载更多
    for _ in range(3):
        bro.execute_script("window.scrollTo(0, document.body.scrollHeight);")
        time.sleep(2)
    
    # 提取商品数据
    items = bro.find_elements(By.CSS_SELECTOR, ".item.J_MouserOnverReq")
    
    # 保存到CSV
    with open('taobao_products.csv', 'w', newline='', encoding='utf-8') as file:
        writer = csv.writer(file)
        writer.writerow(['商品名称', '价格', '销量', '店铺'])
        
        for item in items:
            try:
                title = item.find_element(By.CSS_SELECTOR, ".title").text
                price = item.find_element(By.CSS_SELECTOR, ".price").text
                sales = item.find_element(By.CSS_SELECTOR, ".deal-cnt").text
                shop = item.find_element(By.CSS_SELECTOR, ".shop").text
                writer.writerow([title, price, sales, shop])
            except:
                continue
                
    print("✅ 数据爬取完成,已保存到taobao_products.csv")
        
finally:
    bro.quit()

💎 总结与最佳实践

  1. 驱动管理

    • 使用WebDriver Manager自动管理驱动版本
    from webdriver_manager.microsoft import EdgeChromiumDriverManager
    driver_path = EdgeChromiumDriverManager().install()
    
  2. 资源释放

    # 使用try-finally确保关闭浏览器
    try:
        # 操作代码
    finally:
        bro.quit()  # 完全关闭浏览器及驱动进程
    
  3. 性能优化

    • 使用无头模式节省资源
    • 复用浏览器会话避免重复登录
    • 禁用图片加载加速页面渲染
      options.add_argument("--blink-settings=imagesEnabled=false")
      
  4. 高级应用

    • 处理浏览器弹窗
    • 管理多窗口和标签页
    • 文件上传下载操作
    • 执行JavaScript代码
      bro.execute_script("window.scrollTo(0, document.body.scrollHeight);")
      

提示:对于大规模数据采集,建议将Selenium与Scrapy等框架结合使用,或选择Playwright等更现代的工具以获得更好的性能和稳定性。

🕸️ Scrapy框架全面指南

📌 Scrapy简介

● 什么是Scrapy?

Scrapy是一个高性能爬虫框架,用于爬取网站数据并提取结构化数据。它集成了:

  • ⚡️ 高性能异步下载
  • 📶 队列管理
  • 🌐 分布式支持
  • 🔍 数据解析
  • 💾 持久化存储等核心功能

● 安装指南

# Mac/Linux
pip install scrapy

# Windows
pip3 install wheel
pip3 install pywin32
# 下载匹配Python版本的Twisted:http://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted
pip3 install Twisted‑20.3.0‑cp38‑cp38‑win_amd64.whl
pip3 install scrapy

🛠️ Scrapy基本使用

● 创建工程

scrapy startproject ProName

目录结构

ProName/
├── spiders/          # 爬虫文件存放目录
│   └── __init__.py
├── items.py          # 数据模型定义
├── middlewares.py    # 中间件
├── pipelines.py      # 管道文件
├── settings.py       # 配置文件
└── scrapy.cfg        # 部署配置

● 创建爬虫文件

cd ProName
scrapy genspider spiderName www.example.com

● 爬虫文件解析(spiders/spiderName.py)

import scrapy

class SpiderNameSpider(scrapy.Spider):
    name = "spiderName"  # 爬虫唯一标识
    allowed_domains = ["www.example.com"]
    start_urls = ["http://www.example.com"]  # 起始URL列表
    
    def parse(self, response):
        # 数据解析方法
        titles = response.css('h1::text').getall()
        for title in titles:
            yield {"title": title}

● 运行爬虫

scrapy crawl spiderName

● 关键配置(settings.py)

ROBOTSTXT_OBEY = False       # 禁用robots协议
LOG_LEVEL = 'ERROR'          # 仅显示错误日志
USER_AGENT = '自定义UA'       # UA伪装

🔍 Scrapy数据解析

● XPath解析技巧

# 返回Selector对象列表
results = response.xpath('//div[@class="item"]')

# 提取数据方法:
data = response.xpath('//a/@href').extract()       # 返回所有结果列表
first_data = response.xpath('//a/text()').extract_first()  # 返回第一个结果

● CSS选择器

# 提取文本
response.css('title::text').get()

# 提取属性
response.css('a::attr(href)').getall()

💾 持久化存储

● 终端指令存储

scrapy crawl spiderName -o data.json
scrapy crawl spiderName -o data.csv

● 基于管道的存储

操作流程

  1. 爬虫文件中解析数据
  2. items.py定义数据结构
  3. 将数据封装成Item对象
  4. 提交给管道
  5. 管道处理持久化
  6. 启用管道配置

● 多管道备份(存储到MySQL+Redis)

pipelines.py

class MysqlPipeline:
    def open_spider(self, spider):
        self.conn = pymysql.connect(host='localhost', user='root', 
                                   password='123456', db='scrapy_db')
    
    def process_item(self, item, spider):
        cursor = self.conn.cursor()
        sql = "INSERT INTO table (col1, col2) VALUES (%s, %s)"
        cursor.execute(sql, (item['field1'], item['field2']))
        self.conn.commit()
        return item  # 必须return item传递给下一个管道
    
    def close_spider(self, spider):
        self.conn.close()

class RedisPipeline:
    def open_spider(self, spider):
        self.r = redis.Redis(host='localhost', port=6379)

    def process_item(self, item, spider):
        self.r.lpush('scrapy_items', json.dumps(dict(item)))
        return item

settings.py启用管道

ITEM_PIPELINES = {
    'myproject.pipelines.MysqlPipeline': 300,
    'myproject.pipelines.RedisPipeline': 400,
}

⚡ 高级技巧

● 手动请求发送

# GET请求
yield scrapy.Request(url, callback=self.parse_detail)

# POST请求
yield scrapy.FormRequest(
    url, 
    formdata={'key': 'value'},
    callback=self.parse_detail
)

● 五大核心组件

  1. 引擎(Engine):数据流处理中枢
  2. 调度器(Scheduler):URL队列管理
  3. 下载器(Downloader):网页内容下载
  4. 爬虫(Spiders):数据解析与提取
  5. 管道(Pipeline):数据持久化处理
    在这里插入图片描述

● 请求传参(深度爬取)

# 发起请求时传递参数
yield scrapy.Request(
    url, 
    callback=self.parse_detail,
    meta={'item_id': item['id']}
)

# 回调函数中接收参数
def parse_detail(self, response):
    item_id = response.meta['item_id']

🛡️ 中间件机制

● 下载中间件(核心用途)

class CustomMiddleware:
    
    # 处理请求
    def process_request(self, request, spider):
        # 1. 设置代理
        request.meta['proxy'] = "http://ip:port"
        
        # 2. 修改UA
        request.headers['User-Agent'] = 'custom_ua'
        
        # 3. 添加Cookie
        request.cookies['token'] = 'value'

    # 处理异常
    def process_exception(self, request, exception, spider):
        # 更换失效代理
        request.meta['proxy'] = get_new_proxy()
        return request

● 代理池实现

class ProxyMiddleware:
    PROXY_http = ['111.222.333.444:5000', ...]
    PROXY_https = ['555.666.777.888:5000', ...]
    
    def process_request(self, request, spider):
        if request.url.startswith('https://'):
            proxy = random.choice(self.PROXY_https)
            request.meta['proxy'] = f'https://{proxy}'
        else:
            proxy = random.choice(self.PROXY_http)
            request.meta['proxy'] = f'http://{proxy}'

🖼️ 大文件下载

from scrapy.pipelines.images import ImagesPipeline

class CustomImagesPipeline(ImagesPipeline):
    
    def get_media_requests(self, item, info):
        # 对图片地址发起请求
        yield scrapy.Request(item['image_url'])
    
    def file_path(self, request, response=None, info=None, *, item=None):
        # 自定义文件保存路径
        return f'images/{item["name"]}.jpg'
    
    def item_completed(self, results, item, info):
        # 返回处理完成的item
        return item

settings.py配置

IMAGES_STORE = '/path/to/images/folder'

🔗 CrawlSpider全站爬取

● 创建CrawlSpider

scrapy genspider -t crawl crawl_spider www.example.com

● 规则化爬取

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule

class MyCrawler(CrawlSpider):
    name = 'crawl_spider'
    start_urls = ['http://www.example.com']
    
    rules = (
        # 提取分页链接
        Rule(LinkExtractor(allow=r'/page/\d+/'), callback='parse_item', follow=True),
        
        # 提取详情页链接
        Rule(LinkExtractor(restrict_css='.detail-link'), callback='parse_detail'),
    )

    def parse_item(self, response):
        # 解析列表页数据
        pass

● 链接提取器参数详解

LinkExtractor(
    allow=r'pattern',     # 允许的正则
    deny='exclude_pattern', # 排除的正则
    restrict_xpaths='//div[@class="items"]', # 限制提取区域
    restrict_css='.items',
    deny_domains=['excluded.com']
)

🔄 增量式爬虫与分布式爬虫精要

🔍 增量式爬虫(智能更新监测)

核心目标:高效识别网站新数据,避免重复爬取

● 三大去重策略
策略位置适用场景实现方式优势
请求前URL检查新页面型网站(新闻/小说章节)Redis Set存储已爬URL节省带宽,防止无效请求
解析后内容校验内容更新型网站(商品价格/比分)生成内容MD5指纹存储精准识别内容变更
存储前数据验证终极数据去重数据库唯一键约束数据层最终保障
# 实战示例:URL+内容双校验
import hashlib
from scrapy_redis import RedisSpider

class SmartSpider(RedisSpider):
    name = 'smart_crawler'
    redis_key = 'crawler:start_urls'

    def parse(self, response):
        # 1. URL去重检查
        if self.server.sismember('crawled:urls', response.url):
            self.logger.info(f"跳过已爬URL: {response.url}")
            return
            
        # 2. 内容更新检查
        content = response.text.encode('utf-8')
        content_hash = hashlib.md5(content).hexdigest()
        if self.server.sismember('content:hashes', content_hash):
            self.logger.info(f"内容未更新: {response.url}")
            return
            
        # 处理新数据
        self.server.sadd('crawled:urls', response.url)
        self.server.sadd('content:hashes', content_hash)
        yield self.process_item(response)
● 进阶技巧
  • 混合策略:URL检查为主 + 关键内容校验(如价格/发布日期)
  • 动态权重:高频更新站点缩短校验间隔
  • 版本快照:对历史内容建立版本快照系统
  • 智能调度:结合scrapy-deltafetch插件实现自动增量

🌐 分布式爬虫(Scrapy-Redis深度指南)

突破限制:解决原生Scrapy无法分布式运行的痛点

● 原生Scrapy分布式瓶颈
独立调度器
独立调度器
独立调度器
机器1
URL队列1
机器2
URL队列2
机器3
URL队列3
数据管道
存储1
数据管道
存储2
数据管道
存储3

问题核心:调度器与管道无法跨机器共享

● Scrapy-Redis解决方案架构
推送/拉取
推送/拉取
推送/拉取
统一调度
统一存储
分布式去重
机器1
Redis服务器
机器2
机器3
共享URL队列
共享数据管道
指纹集合
● 实现三部曲
  1. 组件安装

    pip install scrapy-redis
    
  2. 爬虫改造(二选一)

    # 普通Spider改造
    from scrapy_redis.spiders import RedisSpider
    
    class MyDistributedSpider(RedisSpider):
        name = 'distributed_spider'
        redis_key = 'start_urls'  # Redis中的起始URL键
    
    # CrawlSpider改造
    from scrapy_redis.spiders import RedisCrawlSpider
    
    class MyCrawlDistributedSpider(RedisCrawlSpider):
        name = 'distributed_crawler'
        redis_key = 'crawl:start_urls'
        rules = ( ... )
    
  3. 关键配置(settings.py)

    # 启用Redis调度器
    SCHEDULER = "scrapy_redis.scheduler.Scheduler"
    
    # 分布式去重
    DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
    SCHEDULER_PERSIST = True  # 暂停时保存状态
    
    # Redis连接
    REDIS_HOST = '192.168.1.100'
    REDIS_PORT = 6379
    REDIS_PARAMS = {'password': 'secure_pass'}
    
    # 分布式管道
    ITEM_PIPELINES = {
        'scrapy_redis.pipelines.RedisPipeline': 300
    }
    
● 集群管理技巧
  1. 动态扩缩容

    # 新增节点
    scrapy runspider myspider.py
    
    # 终止节点
    Ctrl+C
    
  2. 任务注入

    redis-cli lpush start_urls https://example.com/page/1
    
  3. 状态监控

    redis-cli monitor  # 实时查看任务流
    redis-cli keys '*' # 查看所有存储键
    
  4. 断点续爬
    启用 SCHEDULER_PERSIST = True 后,重启自动继续


💡 混合架构建议(增量式+分布式)

URL分配
数据+指纹
数据+指纹
数据+指纹
新数据
重复数据
管理节点
Redis
爬虫节点1
爬虫节点2
爬虫节点3
Redis
去重分析
持久化存储
丢弃

最佳实践:在分布式集群中,每个节点同时实施增量校验,通过Redis共享去重指纹,实现高效全局去重

通过这种架构,既能横向扩展爬取能力,又能智能识别内容更新,完美平衡效率与资源消耗!


🚀 性能优化技巧

优化方向配置项推荐值
并发请求数CONCURRENT_REQUESTS100
下载延迟DOWNLOAD_DELAY0.25
禁用CookieCOOKIES_ENABLEDFalse
禁用重试RETRY_ENABLEDFalse
下载超时DOWNLOAD_TIMEOUT30
启用自动限速AUTOTHROTTLE_ENABLEDTrue
日志级别LOG_LEVEL‘ERROR’
# settings.py 优化配置示例
CONCURRENT_REQUESTS = 100
DOWNLOAD_DELAY = 0.25
COOKIES_ENABLED = False
RETRY_ENABLED = False
DOWNLOAD_TIMEOUT = 30
AUTOTHROTTLE_ENABLED = True
LOG_LEVEL = 'ERROR'

💡 最佳实践建议

  1. 使用Item Loaders:简化数据处理流程
  2. 启用缓存HTTPCACHE_ENABLED = True 减少重复请求
  3. 中间件优先级:合理安排中间件执行顺序
  4. 使用布隆过滤器:超大规模URL去重
  5. 监控系统:集成Scrapy-Sentry等监控工具
  6. 自动化部署:使用Scrapyd管理爬虫服务

掌握这些技巧,你的Scrapy爬虫将具备工业级强度!💪 根据实际需求灵活组合使用,可轻松应对各种复杂爬取场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值