资讯动态

Python建筑市场数据采集与入库工程源码解析

发布时间:2026/9/23 22:37:38 来源:尧图企业网站定制
简介这份源码面向建筑行业研究人员、市场分析师及企业决策者也适合具备一定Python基础、希望练习数据分析与爬虫实战的开发者。项目以Python为核心围绕全国建筑市场的数据采集、存储与分析展开通过token、image、qy、utils、dao、biz等模块划分职责分别承担权限验证、图像处理、业务逻辑、工具方法与数据持久化等功能结构清晰、便于二次开发。压缩包共64个文件约2.96MB包含15个py源码、14个pyc编译文件、27个png图表、6个xml配置及txt说明等其中png多用于呈现分析结果xml负责配置与数据交换。目前已有228人学习下载。读者可借此获得一套完整的市场分析项目骨架理解模块化分层设计思路并参考图表输出与数据库访问代码快速搭建自己的分析流程。1. 全国建筑市场分析源码一份能跑通的 Python 数据采集与入库工程如果你手头正好有一批建筑市场数据要整理又不想从零搭采集、解析、入库的架子这份基于 Python 的全国建筑市场分析设计源码值得先拆一遍。它不是一个空壳 Demo而是一套带完整目录结构的工程token模块管会话凭证qy模块抓企业信息dao层封装 MySQL 读写utils放通用工具image目录里躺着 27 张验证码截图biz目录承载业务逻辑。整套代码围绕「采集—解析—落库—分析」这条链路展开适合做建筑行业市场研究、企业信息聚合的从业者拿来当脚手架。下面我按实际拆包顺序把模块职责、运行步骤和几个容易翻车的点讲清楚。2. 拆开目录看架构token、qy、dao 三层怎么串起来拿到压缩包解压后第一件事不是急着pip install而是先把目录树看清楚。这份源码的模块划分其实挺典型属于「采集层 工具层 持久层」的经典三段式理解了这个分层后面改代码才不会迷路。2.1 模块职责与调用关系先看根目录下的几个包token/里面是hq_token.py和chaojiying.py。前者负责获取和刷新会话 token后者对接打码平台处理验证码。这两个文件是整个采集链路的前置依赖token 拿不到后面所有请求都会被拒。qy/核心业务包qyxx.py是企业信息采集的主逻辑。qy大概率是「企业」的拼音首字母缩写这个文件里应该包含请求构造、响应解析、字段提取的完整流程。dao/数据访问层Mysql.py封装了数据库连接和增删改查。所有落库操作都走这一层不直接在业务代码里写 SQL这个习惯值得保留。utils/util.py放通用函数dbmysql.py可能是数据库配置或连接池管理。biz/base.py是业务基类具体业务模块继承它。image/27 张 PNG文件名格式是2019-12-19_时间戳.png明显是采集过程中保存的验证码原图用于调试打码逻辑。temp10.py、temp14.py临时脚本可能是开发过程中的测试入口正式跑的时候可以忽略。调用关系上典型流程是hq_token.py先跑拿到 token 写入某处文件或内存qyxx.py读取 token构造请求遇到验证码就调chaojiying.py识别解析出的结构化数据通过dao/Mysql.py写入数据库。utils和biz是横切支撑不直接参与主流程但被各层引用。2.2 环境准备与依赖安装这份代码的.pyc文件显示是 Python 3.7 编译的所以建议用 3.7 或 3.8 跑别上 3.11否则某些老库的兼容性会让你头疼。依赖方面从模块名和常见做法推断至少需要这些# 建议先建虚拟环境避免污染全局 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 核心依赖版本按你实际环境微调 pip install requests pip install pymysql pip install pillow pip install lxml pip install pandasrequests负责 HTTP 请求pymysql是 MySQL 驱动pillow处理验证码图片lxml解析 HTMLpandas用于后续数据分析。如果你用的是chaojiying.py里对接的打码平台还需要确认它的 SDK 是否已经内置没有的话按平台文档补装。提示源码里没有requirements.txt上面这份清单是我根据模块引用反推的。跑之前先用python -c import requests, pymysql, PIL, lxml, pandas验证一遍缺什么补什么。2.3 数据库表结构初始化dao/Mysql.py里应该有建表语句或者至少能看出表结构。如果没找到按企业信息采集的常见字段建一张基础表CREATE TABLE qy_info ( id INT AUTO_INCREMENT PRIMARY KEY, qy_name VARCHAR(255) DEFAULT NULL COMMENT 企业名称, qy_code VARCHAR(64) DEFAULT NULL COMMENT 统一社会信用代码, legal_person VARCHAR(64) DEFAULT NULL COMMENT 法定代表人, reg_capital VARCHAR(64) DEFAULT NULL COMMENT 注册资本, build_date DATE DEFAULT NULL COMMENT 成立日期, address VARCHAR(512) DEFAULT NULL COMMENT 注册地址, business_scope TEXT COMMENT 经营范围, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_qy_name (qy_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段类型上reg_capital用 VARCHAR 而不是 DECIMAL是因为原始数据里经常带「万元」「人民币」这类单位直接存字符串后续清洗更灵活。business_scope用 TEXT因为经营范围可能很长。索引加在qy_name上方便后续按企业名去重或查询。建完表后把utils/dbmysql.py里的连接配置改成你自己的# utils/dbmysql.py 里通常会有类似这样的配置 DB_CONFIG { host: 127.0.0.1, port: 3306, user: root, password: your_password, # 改成你的 database: building_market, charset: utf8mb4 }改完先跑一个连接测试确认能通再往下走。这一步看着简单但后面所有采集数据都要落库连接不通等于白干。3. token 与验证码采集链路的前置关卡建筑市场数据源大多有反爬机制token 和验证码是两道最常见的门槛。这份源码把这两块单独拆成token包说明作者在实战中踩过坑知道这两件事必须独立处理、独立调试。3.1 hq_token.py 的会话维持逻辑hq_token.py的核心任务是拿到一个有效的会话凭证并在过期前刷新。常见做法是模拟登录请求从响应头或 Cookie 里提取 token然后存到本地文件或内存变量里供其他模块读取。# token/hq_token.py 的核心逻辑示意 import requests import json import time TOKEN_FILE token_cache.json def get_token(session): 模拟登录并提取 token login_url https://example.com/api/login # 替换为实际地址 payload { username: your_account, password: your_password } resp session.post(login_url, datapayload, timeout10) if resp.status_code ! 200: raise RuntimeError(f登录失败状态码 {resp.status_code}) data resp.json() token data.get(data, {}).get(token) if not token: raise RuntimeError(响应里没有 token 字段检查接口是否变更) # 写入本地缓存带上时间戳 cache {token: token, ts: time.time()} with open(TOKEN_FILE, w) as f: json.dump(cache, f) return token def load_token(): 优先读缓存过期则重新获取 try: with open(TOKEN_FILE, r) as f: cache json.load(f) # 假设 token 有效期 2 小时 if time.time() - cache[ts] 7200: return cache[token] except (FileNotFoundError, json.JSONDecodeError): pass return None这段代码的关键点有三个一是用session对象保持 Cookie避免每次请求都重新登录二是 token 落盘时带时间戳方便判断是否过期三是load_token先读缓存再决定是否重新获取减少不必要的登录请求。参数上timeout10是防止请求卡死7200是假设的有效期实际要根据目标站点的 token 策略调整。3.2 chaojiying.py 打码对接与 image 目录的用途image/目录里那 27 张 PNG 不是随便放的它们是验证码识别的调试素材。chaojiying.py对接的是打码平台从文件名看是超级鹰核心流程是截图验证码 → 保存到本地 → 调用平台 API 识别 → 拿到识别结果填入表单。# token/chaojiying.py 的调用示意 import requests from PIL import Image import io def recognize_captcha(image_bytes, codetype1902): 调用打码平台识别验证码 codetype: 1902 是常见四位数字字母混合类型按平台文档调整 url http://upload.chaojiying.net/Upload/Processing.php files {userfile: (captcha.png, image_bytes, image/png)} data { user: your_username, pass2: your_password, softid: your_softid, codetype: str(codetype) } resp requests.post(url, filesfiles, datadata, timeout15) result resp.json() if result.get(err_no) ! 0: raise RuntimeError(f打码失败: {result.get(err_str)}) return result[pic_str]codetype这个参数很关键不同验证码类型对应不同编号填错了识别率会暴跌。image/目录里的截图可以用来做两件事一是肉眼确认验证码样式二是批量测试不同codetype的识别准确率。我一般会先拿几张图跑一遍看识别结果和实际是否对得上再决定正式采集时用哪个类型。注意打码平台的账号密码不要硬编码在代码里建议用环境变量或单独的配置文件避免提交到仓库后泄露。3.3 采集主流程的串联把 token 和打码串起来qyxx.py的主流程大致是这样# qy/qyxx.py 的主流程示意 import requests from token.hq_token import load_token, get_token from token.chaojiying import recognize_captcha from dao.Mysql import MysqlDao def crawl_company_list(keyword, pages5): session requests.Session() token load_token() or get_token(session) headers {Authorization: fBearer {token}} dao MysqlDao() for page in range(1, pages 1): url fhttps://example.com/api/company?kw{keyword}page{page} resp session.get(url, headersheaders, timeout10) if resp.status_code 401: # token 过期重新获取后重试 token get_token(session) headers[Authorization] fBearer {token} resp session.get(url, headersheaders, timeout10) data resp.json() for item in data.get(list, []): dao.insert_company(item) # 遇到验证码页面时 if captcha in resp.url: img_resp session.get(https://example.com/captcha, timeout10) code recognize_captcha(img_resp.content) session.post(https://example.com/verify, data{code: code})这段代码里401 重试和验证码处理是两个必须有的分支。没有 401 重试token 一过期整个采集就断了没有验证码处理遇到反爬页面直接卡死。pages参数控制翻页数量建议先设小一点跑通再放大。4. 数据落库与字段清洗dao 层怎么改才不丢数据采集回来的原始数据往往很脏直接塞进数据库后面分析会痛不欲生。dao/Mysql.py这一层除了做插入还应该承担基本的字段清洗和去重职责。4.1 Mysql.py 的插入与去重先看dao/Mysql.py里典型的插入逻辑以及我建议补上的去重判断# dao/Mysql.py 的插入与去重示意 import pymysql from utils.dbmysql import DB_CONFIG class MysqlDao: def __init__(self): self.conn pymysql.connect(**DB_CONFIG) self.cursor self.conn.cursor() def insert_company(self, item): 插入企业信息按企业名称去重 qy_name item.get(qy_name, ).strip() if not qy_name: return # 名称为空直接跳过 # 先查是否已存在 self.cursor.execute( SELECT id FROM qy_info WHERE qy_name %s, (qy_name,) ) if self.cursor.fetchone(): return # 已存在跳过 sql INSERT INTO qy_info (qy_name, qy_code, legal_person, reg_capital, build_date, address, business_scope) VALUES (%s, %s, %s, %s, %s, %s, %s) self.cursor.execute(sql, ( qy_name, item.get(qy_code, ), item.get(legal_person, ), item.get(reg_capital, ), item.get(build_date) or None, item.get(address, ), item.get(business_scope, ) )) self.conn.commit()去重逻辑放在插入前做SELECT判断虽然多一次查询但能避免重复数据污染分析结果。如果数据量很大可以改成INSERT ... ON DUPLICATE KEY UPDATE但前提是qy_name上建了唯一索引。4.2 字段清洗的常见规则原始数据里几个高频脏字段和处理方式字段常见脏数据形态清洗规则qy_name前后空格、全角括号strip 全角转半角reg_capital「1000万元人民币」正则提取数字单位统一为万元build_date「2019-12-19」「2019/12/19」统一转 YYYY-MM-DDaddress多余换行、制表符替换空白字符为单个空格business_scope超长文本、HTML 标签去标签、截断到合理长度清洗代码建议放在utils/util.py里dao 层调用# utils/util.py 的清洗函数示意 import re def clean_capital(raw): 从『1000万元人民币』提取 1000 if not raw: return match re.search(r([\d.]), str(raw)) return match.group(1) if match else def clean_date(raw): 统一日期格式为 YYYY-MM-DD if not raw: return None raw str(raw).replace(/, -).replace(., -) match re.search(r(\d{4})-(\d{1,2})-(\d{1,2}), raw) if match: y, m, d match.groups() return f{y}-{int(m):02d}-{int(d):02d} return None这两个函数看着简单但能省掉后面用 pandas 分析时大量的类型转换麻烦。clean_capital只提数字单位统一在入库前确认clean_date把各种分隔符统一成短横线再补零。4.3 批量插入的性能取舍逐条插入在数据量上千后会很慢常见优化是攒一批再executemanydef insert_batch(self, items, batch_size100): 批量插入每 100 条提交一次 sql INSERT INTO qy_info (qy_name, qy_code, legal_person, reg_capital, build_date, address, business_scope) VALUES (%s, %s, %s, %s, %s, %s, %s) buffer [] for item in items: buffer.append(( item.get(qy_name, ).strip(), item.get(qy_code, ), item.get(legal_person, ), clean_capital(item.get(reg_capital, )), clean_date(item.get(build_date)), item.get(address, ).replace(\n, ).replace(\t, ), item.get(business_scope, ) )) if len(buffer) batch_size: self.cursor.executemany(sql, buffer) self.conn.commit() buffer [] if buffer: self.cursor.executemany(sql, buffer) self.conn.commit()batch_size100是个折中值太小提交频繁太大内存占用高且失败回滚代价大。实际跑的时候根据机器配置和目标库性能调我一般从 100 开始试稳定的话加到 500。5. 避坑与排查跑这份源码时最容易翻车的五个点这套代码我拆完跑了一遍下面五个问题是实际调试中大概率会遇到的按「现象 → 原因 → 解决」列出来省得你一个个试。5.1 现象跑 qyxx.py 报 ModuleNotFoundError原因源码用的是相对导入或包内导入但你是直接在文件所在目录执行python qyxx.pyPython 找不到上级包路径。解决在项目根目录执行用python -m qy.qyxx的方式跑或者在qyxx.py开头临时加上sys.path.append把根目录加进去。更规范的做法是在根目录建一个run.py作为统一入口。5.2 现象token 获取成功但后续请求全部 401原因hq_token.py拿到的 token 可能没有正确写入请求头或者 token 字段名和目标站点要求的不一致。有些站点要求token放在 Cookie 里而不是Authorization头。解决先用requests手动发一个请求把 token 分别放在 header 和 cookie 里试看哪种能通。确认后改qyxx.py里的请求构造部分。另外检查 token 是否有前缀要求比如Bearer不能少。5.3 现象验证码识别率极低十张错八张原因codetype参数填错了或者验证码图片在保存时被压缩导致模糊。解决从image/目录挑几张图手动上传到打码平台测试不同codetype找到识别率最高的那个。保存图片时用img_resp.content原始字节流不要用PIL重新保存避免二次压缩。5.4 现象数据插入时报字段超长或编码错误原因business_scope字段实际内容超过 TEXT 限制或者数据库字符集不是utf8mb4导致生僻字报错。解决建表时确认CHARSETutf8mb4business_scope用MEDIUMTEXT或LONGTEXT。插入前对超长字段做截断比如business_scope[:5000]。5.5 现象跑一段时间后程序卡死无响应原因requests请求没有设timeout遇到慢响应一直等或者数据库连接长时间不提交导致锁等待。解决所有requests调用加timeout10数据库操作每批提交一次。另外在循环里加time.sleep(1)控制频率别把目标站点打挂。6. 从采集到分析用 pandas 做一次建筑市场企业分布统计数据落库后这份源码的价值才真正体现出来。image/目录里的图表是静态结果但你可以用 pandas 直接从数据库拉数据做动态分析。下面是我常用的一个分析脚本按地区统计企业数量和注册资本分布。# analysis/market_stats.py import pandas as pd import pymysql from utils.dbmysql import DB_CONFIG def load_data(): conn pymysql.connect(**DB_CONFIG) sql SELECT qy_name, reg_capital, build_date, address FROM qy_info df pd.read_sql(sql, conn) conn.close() return df def analyze(df): # 注册资本转数值 df[capital_num] pd.to_numeric(df[reg_capital], errorscoerce) # 按地区分组统计 df[province] df[address].str.extract(r(北京|上海|广东|江苏|浙江|山东|河南|四川)) stats df.groupby(province).agg( qy_count(qy_name, count), avg_capital(capital_num, mean), max_capital(capital_num, max) ).sort_values(qy_count, ascendingFalse) print(stats) return stats if __name__ __main__: df load_data() print(f总记录数: {len(df)}) stats analyze(df) stats.to_csv(market_stats.csv, encodingutf-8-sig)这段脚本的关键在str.extract那一步用正则从地址里提取省份。实际地址格式可能更复杂比如「北京市朝阳区」和「北京朝阳区」都有正则要覆盖常见变体。errorscoerce让无法转数值的注册资本变成 NaN避免整列报错。最后导出 CSV 时用utf-8-sigExcel 打开不乱码。跑完这个脚本你会得到一张按省份汇总的企业数量表比image/目录里的静态图灵活得多——换个正则就能按城市统计加个时间维度就能看趋势。从那以后我每次拿到这类采集源码都习惯先跑通落库再用 pandas 接一层分析而不是直接看作者给的截图。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价