查看全课大纲(第 16 / 52 节)
  1. 1.量化分析基本概念:从咖啡馆到华尔街
  2. 2.量化交易员学习路线:从零基础到策略开发
  3. 3.量化策略生命周期:数据分析的五大流程
  4. 4.量化数据类型:数值型与类别型数据
  5. 5.量化沙盒搭建(1):Python与Anaconda安装
  6. 6.量化沙盒搭建(2):Jupyter Notebook与库配置
  7. 7.本地多源数据:量化历史回测的基石
  8. 8.TXT数据流操作:处理金融交易日志
  9. 9.JSON数据流操作:解析股票行情API响应
  10. 10.CSV行情文件操作:读取与存储历史K线
  11. 11.Excel报表操作:清洗财报与财务指标
  12. 12.量化数据库:SQL常用查询语法
  13. 13.量化数据库:Python连接与写入
  14. 14.量化多表连接:关联股票信息与行情
  15. 15.实战:小象咖啡店营业数据与个股财务指标的多源数据分析
  16. 16.量化数据采集:爬虫工作原理与合规
  17. 17.量化爬虫核心:URL管理与去重策略
  18. 18.量化网页下载:防反爬与请求头伪装
  19. 19.网页解析(1):使用XPath定位财经新闻
  20. 20.网页解析(2):使用CSS选择器与ItemLoader
  21. 21.Scrapy框架:构建工业级量化爬虫
  22. 22.Scrapy实战(1):新建项目与数据模型定义
  23. 23.Scrapy实战(2):编写财经爬虫逻辑
  24. 24.Scrapy实战(3):启用数据管道清洗行情
  25. 25.Scrapy实战(4):防反爬中间件配置
  26. 26.实战:构建每日热门个股行情自动采集系统
  27. 27.NumPy与SciPy:加速量化矩阵计算
  28. 28.NumPy数组:构建股票价格矩阵
  29. 29.数组切片:提取特定股票与交易日数据
  30. 30.NumPy常用方法:计算均值与波动率
  31. 31.向量化计算:摆脱缓慢的For循环
  32. 32.通用函数ufunc:计算对数收益率与累计收益
  33. 33.实战:股票组合资产收益率与协方差矩阵计算
  34. 34.Pandas Series:管理单只股票收盘价
  35. 35.Pandas DataFrame:构建多只股票行情面板
  36. 36.Pandas Index:时间序列索引与对齐
  37. 37.DataFrame索引操作:loc与iloc定位行情
  38. 38.索引总结:量化多字段筛选与定位
  39. 39.运算与对齐:合并不同交易日的数据
  40. 40.Pandas map:对股票代码与状态做映射
  41. 41.Pandas apply:滚动计算技术指标
  42. 42.排序操作:按市盈率与收益率对股票排名
  43. 43.数据清洗:填充与剔除停牌股票缺失值
  44. 44.数据清洗:剔除重复报价与异常交易数据
  45. 45.常用统计:量化最大回撤与分位数风险评估
  46. 46.极值定位:寻找历史最高价与最低价的发生日
  47. 47.风险与累计收益:计算年化波动率与累计回报
  48. 48.实战:小象咖啡店营业数据与热门个股时间序列清洗分析
  49. 49.Series索引操作:按位置与标签访问行情数据
  50. 50.文件读写操作:CSV行情数据导入导出
  51. 51.数据清洗:替换异常报价数据
  52. 52.常用统计:sum/mean/median/count聚合指标

量化数据采集:爬虫工作原理与合规

约 8 分钟

小象实战讲义 · 量化交易Python入门:从咖啡馆到华尔街

本章我们将探寻如何使用 Scrapy 框架进行网络数据采集。作为量化分析师,你将编写爬虫从财经网站爬取最新的股票行情和社交媒体上的咖啡店舆情,为策略引擎输入第一手另类数据。

💡 核心导读

本节将带你认识网络爬虫的核心概念与技术原理。通过实战演示,你将掌握:1)HTTP请求与响应基础;2)网页解析的三种主流方法;3)使用Scrapy框架创建基础爬虫。重点学习如何通过XPath定位元素,以及处理动态加载内容的技巧。最终完成一个能自动提取电商产品信息的可运行爬虫示例。


爬虫工作原理

基础通信流程

网络爬虫本质是模拟浏览器行为的自动化程序,核心流程为:

# 块 1: 模拟HTTP请求流程的伪代码
print("HTTP请求流程:发送请求 -> 接收响应 -> 解析内容 -> 存储数据")

关键组件对比

HTTP请求流程:发送请求 -> 接收响应 -> 解析内容 -> 存储数据

实战:豆瓣图书爬虫

安装必备库

pip install scrapy parsel requests

基础请求示例

# 块 2: 模拟网页解析,使用内存中的HTML字符串
# 不使用 pandas.read_html 以避免 lxml 依赖,直接解析字符串
html_content = """
<html>
<body>
<div class="pl2"><a title="股票量化分析实战">股票量化分析实战</a></div>
<div class="pl2"><a title="Python金融大数据分析">Python金融大数据分析</a></div>
<div class="pl2"><a title="投资学">投资学</a></div>
<div class="pl2"><a title="漫步华尔街">漫步华尔街</a></div>
</body>
</html>
"""

# 简单字符串解析模拟
lines = html_content.split('\n')
titles = []
for line in lines:
    if 'title="' in line:
        start = line.find('title="') + 7
        end = line.find('"', start)
        title = line[start:end]
        titles.append(title)

print(f"获取到{len(titles)}本书籍:")
for title in titles[:3]:
    print(f"  - {title}")

输出示例

获取到4本书籍:
  - 股票量化分析实战
  - Python金融大数据分析
  - 投资学


Scrapy框架速成

创建爬虫项目

scrapy startproject douban_books
cd douban_books
scrapy genspider book_spider book.douban.com

核心组件配置

# 块 3: 模拟Scrapy爬虫结构,但不执行真实爬取
print("Scrapy爬虫类定义示例(不执行)")
print("class BookSpider(scrapy.Spider):")
print("    name = 'book_spider'")
print("    start_urls = ['https://book.douban.com/top250']")
print()
print("    def parse(self, response):")
print("        # 解析逻辑")
print("        yield {'title': '示例', 'rating': '9.0', 'price': '59.00'}")
Scrapy爬虫类定义示例(不执行)
class BookSpider(scrapy.Spider):
    name = 'book_spider'
    start_urls = ['https://book.douban.com/top250']

    def parse(self, response):
        # 解析逻辑
        yield {'title': '示例', 'rating': '9.0', 'price': '59.00'}

执行爬虫

scrapy crawl book_spider -o books.json


反爬应对策略

常见防御手段

  1. User-Agent检测
  2. IP频率限制
  3. 验证码挑战
  4. 动态数据加载(AJAX)

解决方案代码

# 块 4: 模拟爬虫配置与中间件
import time

# 模拟配置
DOWNLOAD_DELAY = 2
ROBOTSTXT_OBEY = True
DEFAULT_REQUEST_HEADERS = {
    'Accept': 'text/html,application/xhtml+xml',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'
}

print("爬虫配置:")
print(f"  下载延迟: {DOWNLOAD_DELAY} 秒")
print(f"  遵守robots.txt: {ROBOTSTXT_OBEY}")
print(f"  默认请求头: {DEFAULT_REQUEST_HEADERS['User-Agent']}")

# 模拟随机User-Agent中间件
class RandomUserAgentMiddleware:
    def process_request(self, request, spider):
        # 模拟随机选择
        agents = ['Mozilla/5.0', 'QuantBot/1.0', 'Chrome/120.0']
        import random
        selected = random.choice(agents)
        request.headers['User-Agent'] = selected
        return None

print("中间件已定义: RandomUserAgentMiddleware")
爬虫配置:
  下载延迟: 2 秒
  遵守robots.txt: True
  默认请求头: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
中间件已定义: RandomUserAgentMiddleware

📝 动手练一练

第 1 题 · 概念应用

你正在为你的量化策略构建一个数据采集模块,需要从某财经网站获取最新的股票行情数据。该网站的 robots.txt 文件中有以下规则:

User-agent: *
Disallow: /api/real-time/
Crawl-delay: 5

同时,你发现该网站对未登录用户隐藏了部分深度财务数据(如机构持仓明细),且该网站的服务条款明确规定:禁止使用注册账户以自动化方式采集数据。以下哪种做法最符合合规、安全且有效的数据采集原则?

  • A. 忽略 robots.txt,因为 /api/real-time/ 路径下的实时数据对你的策略至关重要。同时,使用多个代理IP轮换,将请求频率提升至每秒10次,以绕过 Crawl-delay: 5 的限制。
  • B. 严格遵守 robots.txt,不抓取 /api/real-time/ 路径,并将请求间隔设置为至少5秒。对于隐藏的深度财务数据,尝试破解网站的登录接口,模拟登录后获取。
  • C. 严格遵守 robots.txt:不抓取 /api/real-time/ 路径,请求间隔不少于 5 秒;对于需要登录才能查看的深度财务数据,放弃抓取,转而寻找其他公开、合法的来源(如交易所官方数据接口或授权数据商)。
  • D. 完全遵守 robots.txt 的所有规则。为了获取隐藏数据,在网站注册一个免费账户,并使用该账户的Cookie来模拟登录状态进行抓取,但将此类请求的频率也控制在 Crawl-delay 要求之内。
👉 点击查看参考答案

答案:C

推理过程:

  1. 合规性:robots.txt 是网站所有者表达爬虫抓取偏好的标准。Disallow 指令应被严格遵守;Crawl-delay 是站点明确给出的最低请求间隔,把它当作“只是建议”而用更短间隔(如3秒)同样违反规则。选项A两条都违反,选项C两条都遵守。
  2. 安全性:尝试“破解”登录接口(选项B)通常违反网站服务条款,并可能触犯法律;使用注册账户的 Cookie 自动化抓取登录后才可见的数据(选项D),也往往超出账户使用协议授权的范围,存在封号和法律风险。
  3. 有效性:量化分析应优先使用稳定、合法的数据源。选项C对禁止或难以合法获取的数据主动寻找替代的公开、合法来源(官方接口、授权数据商),既是合规要求,也保证了数据采集的长期稳定性。A、B、D 的做法都建立在可能损害目标网站或违反协议的基础上,不可取。

💡 小象贴士:在量化实战中,数据源的合规性与稳定性优先于数据的“独家性”。优先使用官方或经授权的数据接口,将网页爬虫作为最后手段,并务必遵守 robots.txt 协议,设置保守的请求延迟(通常比 Crawl-delay 更长),并做好请求失败的重试与退避处理。


第 2 题 · 代码填空

合规采集的第一步,是让程序读懂目标站点的 robots.txt。下面的代码把规则解析为「禁止路径前缀列表 + 抓取间隔」,请补全判断函数中的关键方法:判断一个待抓取路径是否命中了任一 Disallow 前缀。

robots_txt = """User-agent: *
Disallow: /api/real-time/
Disallow: /admin/
Crawl-delay: 5
"""

# 解析 robots.txt:收集 Disallow 路径与 Crawl-delay
disallowed = []
crawl_delay = None
for line in robots_txt.splitlines():
    line = line.strip()
    if line.startswith('Disallow:'):
        disallowed.append(line.split(':', 1)[1].strip())
    elif line.startswith('Crawl-delay:'):
        crawl_delay = int(line.split(':', 1)[1].strip())

def can_fetch(path):
    # 路径命中任一 Disallow 前缀即禁止采集
    return not any(path.______(prefix) for prefix in disallowed)

for path in ['/api/real-time/AAPL.json', '/news/latest.html', '/admin/users']:
    decision = '允许抓取' if can_fetch(path) else '禁止抓取'
    print(f"{path} -> {decision}(请求间隔 >= {crawl_delay} 秒)")
👉 点击查看参考答案

空位应填:startswith

补全后的完整可运行代码:

robots_txt = """User-agent: *
Disallow: /api/real-time/
Disallow: /admin/
Crawl-delay: 5
"""

# 解析 robots.txt:收集 Disallow 路径与 Crawl-delay
disallowed = []
crawl_delay = None
for line in robots_txt.splitlines():
    line = line.strip()
    if line.startswith('Disallow:'):
        disallowed.append(line.split(':', 1)[1].strip())
    elif line.startswith('Crawl-delay:'):
        crawl_delay = int(line.split(':', 1)[1].strip())

def can_fetch(path):
    # 路径命中任一 Disallow 前缀即禁止采集
    return not any(path.startswith(prefix) for prefix in disallowed)

for path in ['/api/real-time/AAPL.json', '/news/latest.html', '/admin/users']:
    decision = '允许抓取' if can_fetch(path) else '禁止抓取'
    print(f"{path} -> {decision}(请求间隔 >= {crawl_delay} 秒)")

运行结果:

/api/real-time/AAPL.json -> 禁止抓取(请求间隔 >= 5 秒)
/news/latest.html -> 允许抓取(请求间隔 >= 5 秒)
/admin/users -> 禁止抓取(请求间隔 >= 5 秒)

解释:

  1. str.startswith(prefix) 判断路径是否以某个 Disallow 前缀开头;/api/real-time/AAPL.json 以前缀 /api/real-time/ 开头,所以被禁。
  2. any(...) 只要命中任意一条禁止前缀就返回 True,外层 not 取反后得到最终“是否允许抓取”的结论;/news/latest.html 不命中任何前缀,允许抓取。
  3. 生产级爬虫还应处理 Allow 与 Disallow 的长短前缀优先级、User-agent 分组等规则,通常直接使用标准库的 urllib.robotparser;本题手写解析是为了看清规则匹配的本质。

本章小结

📋 行动清单

  • 使用requests+parsel抓取豆瓣图书Top250的图书信息
  • 创建Scrapy项目抓取京东商品页面的价格和评论数
  • 实现自动翻页功能,连续抓取5页知乎热榜问题

「小象量化教研组」提示:请遵守robots.txt协议,控制请求频率避免对目标服务器造成压力。

—— 小象教研组