Python爬虫实战:股票信息采集系统源码解析与二次开发 简介这份PDF面向具备一定Python基础、希望系统掌握网络爬虫设计与实现的开发者与在校学生围绕股票信息抓取场景讲解从页面采集、解析到数据存储与可视化的完整链路。内容涵盖爬虫系统的基本结构与运行流程Requests多线程处理URL、BeautifulSoup解析HTML、Scrapy框架定向抓取等核心工具的选型与配合并给出股票网页采集、解析、数据挖掘、信息维护与浏览五个模块的划分以及软硬件环境配置建议。资源包共1个PDF文件约1.2MB为期刊论文排版含摘要、关键词、系统分析与可行性论证等规范章节便于读者理解方案论证思路并迁移到自己的爬虫项目。已有486人学习适合作为课程设计或小型数据采集系统的参考范本。1. 从一份“股票信息采集系统”说起Python 爬虫项目到底能跑出什么很多人第一次接触 Python 爬虫都是从“抓一个网页、打印几行标题”开始的但真正拿到一份完整的爬虫项目源码时反而不知道从哪下手。这份基于 Python 的网络爬虫系统核心目标很明确定向抓取股票网页信息解析后结构化入库再通过 Web 界面做数据浏览和可视化呈现。它不是一个玩具脚本而是一套带用户角色、带数据库、带前后端分离结构的完整工程适合想从“会写 requests”进阶到“能搭一套采集系统”的开发者。项目采用 Flask 做后端、Vue 做前端、PostgreSQL 存数据爬虫侧用 Scrapy 做定向抓取、BeautifulSoup 做页面解析、Requests 做多线程 URL 处理。如果你手里正拿着一份类似的源码包想把它跑起来、看懂结构、再改成自己的采集目标下面这套拆解路径可以直接照着走。2. 技术栈与目录结构先搞清楚每个文件夹在干什么2.1 后端 Flask 爬虫 Scrapy 的双层结构这个项目的代码组织不是把所有逻辑塞进一个app.py而是按职责拆成了爬虫层和 Web 服务层。常见做法是根目录下分spider/、web/、static/、templates/几个大块爬虫层独立运行、Web 层独立启动两者通过数据库解耦。这样拆的好处是爬虫挂了不影响页面访问页面改版也不用动采集逻辑。一个典型的目录结构大致如下stock_spider/ ├── spider/ # 爬虫工程 │ ├── scrapy.cfg # Scrapy 项目配置入口 │ ├── stock_spider/ │ │ ├── items.py # 字段定义 │ │ ├── pipelines.py # 入库管道 │ │ ├── settings.py # 并发、延迟、UA 配置 │ │ └── spiders/ │ │ └── stock.py # 定向抓取逻辑 ├── web/ # Flask 服务 │ ├── app.py # 应用入口 │ ├── models.py # 数据模型 │ ├── views/ # 路由与接口 │ └── config.py # 数据库连接配置 ├── static/ # 前端静态资源 ├── templates/ # 页面模板 └── requirements.txtspider/下是 Scrapy 的标准骨架items.py定义要抓哪些字段pipelines.py负责把解析结果写进 PostgreSQLsettings.py控制并发数和下载延迟。web/下是 Flask 应用models.py对应数据库表结构views/里按功能拆路由。前端 Vue 部分通常单独放在frontend/或直接编译进static/取决于源码包的组织方式。提示拿到源码后先看requirements.txt和scrapy.cfg这两个文件决定了你装什么依赖、从哪个目录启动爬虫比通读代码快得多。2.2 数据库三张核心表与字段对应关系项目的数据落点集中在三张表gp_user用户信息、gp_info股票基础数据、gp_details股票详细数据。字段设计直接对应爬虫解析出来的内容理解表结构等于理解了爬虫要抓什么。表名关键字段用途gp_userusername、password、email、role、r_time用户注册登录与角色区分gp_infoshares_f12代码、shares_f14名称、shares_f2最新价、shares_f3涨跌幅股票列表页基础行情gp_detailsshares_time、shares_price、shares_num、shares_avg_price个股分时/历史明细gp_info里的shares_f12、shares_f14这种命名看起来别扭其实是抓取源页面字段编号的直接映射改采集目标时这几个字段名要跟着换。gp_details里shares_price出现了多次最低价、当前价、最高价实际建表时通常加后缀区分源码里如果没区分入库时容易串列这是后面排查章节要重点说的坑。2.3 运行环境与依赖安装项目正文给出的环境要求是 Python 2.7 及以上、PostgreSQL 9.3、Nginx 1.6。实际跑的时候我建议直接上 Python 3.8因为 Scrapy 和 Flask 的新版本对 Python 2 支持已经断了硬用 2.7 会在装依赖时卡住。依赖安装分两步# 创建虚拟环境避免污染系统 Python python3 -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装项目依赖 pip install -r requirements.txt # 如果 requirements.txt 缺失按核心包手动装 pip install scrapy beautifulsoup4 requests flask psycopg2-binarypsycopg2-binary是 PostgreSQL 的 Python 驱动装 binary 版本可以省掉本地编译依赖。数据库建好后把web/config.py里的连接串改成自己的# web/config.py DB_CONFIG { host: 127.0.0.1, port: 5432, database: stock_db, user: postgres, password: your_password }参数说明host和port按实际数据库地址填database要先在 PostgreSQL 里CREATE DATABASE stock_db;建好user和password用你有建表权限的账号。改完先跑一次建表脚本源码里一般在web/下有个init_db.py或 SQL 文件确认三张表存在再启动服务。3. 核心模块拆解一个股票页面从请求到入库的完整链路3.1 Scrapy 定向抓取spider 里怎么定义起始 URL 和翻页爬虫的入口在spider/stock_spider/spiders/stock.py。定向抓取的关键是start_urls和parse方法前者给初始链接后者处理响应并决定是否继续翻页。import scrapy from stock_spider.items import StockItem class StockSpider(scrapy.Spider): name stock allowed_domains [example-stock.com] start_urls [http://example-stock.com/list?page1] def parse(self, response): # 解析列表页每一行股票数据 for row in response.css(table.stock-list tr): item StockItem() item[code] row.css(td:nth-child(1)::text).get() item[name] row.css(td:nth-child(2)::text).get() item[price] row.css(td:nth-child(3)::text).get() item[change] row.css(td:nth-child(4)::text).get() yield item # 翻页找到下一页链接继续请求 next_page response.css(a.next::attr(href)).get() if next_page: yield response.follow(next_page, callbackself.parse)逻辑说明allowed_domains限制爬虫只在这个域名下活动防止误爬到外站start_urls是入口列表页parse里先用 CSS 选择器逐行提取字段yield item把数据交给 pipeline最后通过response.follow跟进下一页形成递归抓取。参数上td:nth-child(n)的序号要和目标页面表格列顺序严格对应改站点时这里最容易错位。settings.py里有两个参数必须调# spider/stock_spider/settings.py DOWNLOAD_DELAY 1.5 # 每次请求间隔防止被封 CONCURRENT_REQUESTS 4 # 并发数别一上来就开大 ITEM_PIPELINES { stock_spider.pipelines.StockPipeline: 300, }DOWNLOAD_DELAY设 1.5 秒是保守值目标站反爬严就往上加CONCURRENT_REQUESTS控制同时发出的请求数新手直接开 16 很容易触发限流。ITEM_PIPELINES里的数字是执行优先级越小越先执行。3.2 BeautifulSoup 解析与 Requests 多线程补抓Scrapy 负责列表页的批量抓取但项目里还用了 Requests BeautifulSoup 做补充采集主要针对需要单独请求的个股详情页。多线程部分用concurrent.futures包一层把一批 URL 丢进线程池并发处理。import requests from bs4 import BeautifulSoup from concurrent.futures import ThreadPoolExecutor HEADERS { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 } def fetch_detail(url): try: resp requests.get(url, headersHEADERS, timeout10) resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) # 按页面结构提取详情字段 price soup.select_one(span.price).get_text(stripTrue) volume soup.select_one(span.volume).get_text(stripTrue) return {url: url, price: price, volume: volume} except Exception as e: return {url: url, error: str(e)} urls [fhttp://example-stock.com/detail/{code} for code in code_list] with ThreadPoolExecutor(max_workers5) as pool: results list(pool.map(fetch_detail, urls))逻辑说明HEADERS里的 User-Agent 是伪装成浏览器请求的必要信息不带的话很多站点直接返回 403resp.apparent_encoding自动推断编码避免中文乱码ThreadPoolExecutor的max_workers5控制并发线程数和 Scrapy 的并发是两套独立机制别同时开太大。timeout10防止某个请求卡死拖垮整批任务异常里把 URL 和错误一起返回方便后面定位是哪个链接出的问题。3.3 Pipeline 入库解析结果怎么写进 PostgreSQLScrapy 抓到的 item 最终由 pipeline 写库。核心是建连接、拼 SQL、批量提交避免每条数据开一次连接。import psycopg2 from stock_spider.settings import DB_CONFIG class StockPipeline: def open_spider(self, spider): # 爬虫启动时建一次连接 self.conn psycopg2.connect(**DB_CONFIG) self.cur self.conn.cursor() def process_item(self, item, spider): sql INSERT INTO gp_info (shares_f12, shares_f14, shares_f2, shares_f3, r_time) VALUES (%s, %s, %s, %s, NOW()) ON CONFLICT (shares_f12) DO UPDATE SET shares_f2 EXCLUDED.shares_f2, shares_f3 EXCLUDED.shares_f3, r_time NOW() self.cur.execute(sql, ( item.get(code), item.get(name), item.get(price), item.get(change) )) self.conn.commit() return item def close_spider(self, spider): self.cur.close() self.conn.close()逻辑说明open_spider和close_spider是 Scrapy 的生命周期钩子连接只建一次、关一次比每条数据都连库快一个量级。ON CONFLICT ... DO UPDATE是 PostgreSQL 的 upsert 语法靠shares_f12唯一约束实现“有则更新、无则插入”这正是项目正文里说的“增量采集”的落点。参数上EXCLUDED指代冲突时准备插入的那行新数据NOW()是数据库服务器时间比 Python 本地时间更可靠。3.4 Flask 接口与 Vue 前端的数据对接Web 层负责把库里的数据吐给前端。Flask 路由按功能拆股票列表接口大致长这样from flask import Flask, jsonify, request from web.models import query_stocks app Flask(__name__) app.route(/api/stocks, methods[GET]) def stock_list(): page int(request.args.get(page, 1)) size int(request.args.get(size, 20)) keyword request.args.get(keyword, ) data query_stocks(page, size, keyword) return jsonify({code: 0, data: data, total: len(data)}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)逻辑说明page和size做分页keyword支持按股票名称或代码模糊查询返回统一结构{code, data, total}方便前端处理。host0.0.0.0让局域网内其他机器也能访问debugTrue只在开发时开上线要关掉。前端 Vue 侧用 axios 请求这个接口把data绑到表格和 ECharts 折线图上就完成了“爬取—入库—展示”的闭环。4. 避坑与排查跑这份源码最容易翻车的五个地方4.1 现象爬虫启动就报 403一条数据抓不到原因目标站点识别出请求不是浏览器发的直接拒绝。解决在settings.py里补全DEFAULT_REQUEST_HEADERS至少带上 User-Agent 和 Referer如果还不行检查是不是ROBOTSTXT_OBEY设成了 True定向采集自己确认过合规的页面时可以设为 False但要清楚这意味着什么。4.2 现象入库后中文全是问号或乱码原因Requests 请求没设编码或者 PostgreSQL 建库时字符集不是 UTF8。解决请求侧加resp.encoding resp.apparent_encoding数据库侧建库时显式指定ENCODING UTF8连接串里也可以加client_encodingutf8。已经建错的库要重建改表字符集在 PostgreSQL 里很麻烦。4.3 现象gp_details表里价格字段串列最低价跑到当前价列原因建表时多个字段都叫shares_price插入时按位置对应顺序一错就串。解决建表时给字段加明确后缀比如shares_price_low、shares_price_now、shares_price_highpipeline 里按字段名显式插入不要依赖列顺序。这是血泪经验串列后数据看着有值实际全是错的排查起来很费时间。4.4 现象爬虫跑一会儿就卡住不动也不报错原因某个请求没有超时线程一直挂着或者目标站返回了验证页解析器拿不到预期元素但没抛异常。解决Requests 侧统一加timeoutScrapy 侧设DOWNLOAD_TIMEOUT解析函数里对select_one的结果做空值判断拿不到就记日志跳过别让 None 往下传。4.5 现象Flask 启动报数据库连接失败但 psql 能连上原因config.py里的连接参数和实际不一致常见的是端口写成 5432 但实际改了端口或者用了localhost而容器里要走宿主 IP。解决先用psql -h 127.0.0.1 -p 5432 -U postgres -d stock_db验证命令行能连再把同样的参数抄进config.py容器场景把host换成数据库服务名或宿主地址。5. 二次开发把股票采集改成你自己的目标站点拿到这份源码后最实际的改造路径是换采集目标。整个链路里需要动的只有四处items.py的字段定义、spider 里的选择器、pipeline 里的建表 SQL、前端展示的字段映射。我一般会先拿目标页面的一小段 HTML 在浏览器里试选择器确认能选中再写进代码避免改完跑一遍才发现选错。具体操作上第一步改items.py把code、name、price换成你目标站点的实际字段第二步改 spider 的start_urls和parse里的 CSS 选择器翻页逻辑如果目标站是 JS 渲染的Scrapy 默认拿不到得换 Splash 或直接调接口第三步改 pipeline 的 INSERT 语句和建表脚本字段名对齐第四步改 Flask 接口返回结构和 Vue 的表格列。验证方法很简单先只抓一页、打印 item 不写库确认字段有值再开 pipeline 写库SELECT count(*)看条数最后开 Web 页面看展示。三步都过再放开并发和翻页。改造点文件验证方式字段定义items.py打印 item 看是否有值页面选择器spiders/stock.py单页抓取不写库入库语句pipelines.py查表 count 和抽样字段接口与展示views/ 前端页面加载看数据还有一个容易被忽略的点增量采集靠的是唯一约束加 upsert换站点后如果新表没有给业务主键加唯一索引ON CONFLICT会直接报错。建表时先把唯一约束加上再跑爬虫。从那以后我每次改采集目标都强制先跑一遍“单页—入库—展示”的最小闭环确认通了再放量这个习惯帮我省掉了不少半夜排查的后悔药。希望帮到你。本文还有配套的精品资源点击获取