尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

豆瓣TOP250爬虫实战:从HTTP抓取到数据清洗的全链路工程化指南

发布时间:2026/9/23 18:54:06

资讯中心
01
ARTICLE

豆瓣TOP250爬虫实战:从HTTP抓取到数据清洗的全链路工程化指南

豆瓣TOP250爬虫实战:从HTTP抓取到数据清洗的全链路工程化指南
简介本资源是一套完整的豆瓣电影TOP250数据采集与可视化分析实战项目面向Python初学者及数据分析入门者解决网页爬虫开发、结构化数据清洗、多维统计分析与前端动态图表呈现等典型学习痛点。压缩包共86个文件约11.17MB包含3个核心Python脚本spider.py、app.py、testWordCloud.py实现爬取、服务部署与词云生成7个HTML页面与配套的17个JS、16个CSS、21个JPG等前端资源构建了含电影列表、评分分布、词云展示、团队信息等模块的交互式Web看板另有Excel原始数据豆瓣电影Top250.xls与Markdown说明文档便于理解数据流向与项目架构。目前已有1000人学习下载读者可直接运行本地Flask服务获得从数据抓取到可视化落地的全流程实践范例并掌握ECharts集成、静态资源组织及常见反爬应对思路。1. 为什么豆瓣电影TOP250爬虫至今仍是Python新手绕不开的“成人礼”它不只教你抓网页更逼你直面反爬、数据清洗与分析闭环的真实战场你写完第一个requests.get()拿到200状态码时以为爬虫已经成功——结果发现页面里全是空div你加了headers和随机User-Agent终于渲染出电影名却卡在翻页逻辑上第2页URL参数是start25第3页是start50但第10页突然跳成start225中间漏了3条你吭哧吭哧存了250条数据进CSV用pandas画出评分分布图却发现《肖申克的救赎》被爬成了“肖申·克的救赎”导演“弗兰克·德拉邦特”变成了“弗兰克·德拉邦特#x5728;”最后导出Excel给同事看对方问“这数据能信吗豆瓣自己都改版三次了你爬的是哪年快照”——这就是豆瓣TOP250爬虫的真实现场它表面是入门练手项目实则是微型全栈数据工程沙盒。它强制你串联HTTP协议理解、DOM结构解析、编码异常处理、增量去重逻辑、字段语义清洗、统计可信度校验六个硬核环节。本文不讲“如何三行代码爬完”而是带你用生产级思维重做一遍从2024年豆瓣当前真实HTML结构出发用requests lxml稳住抓取用pandas numpy做脏数据手术用matplotlib seaborn验证分布合理性并把所有坑——包括XPath失效、Unicode转义、评分字段错位、标题重复采集——全部摊开写成可复现的排查清单。适合刚学完Python基础、正卡在“学完语法却不会干活”临界点的工程师也适合想带新人做数据工程启蒙的TL。2. 抓取层绕过豆瓣前端渲染陷阱用纯静态请求精准XPath定位真实数据节点豆瓣电影TOP250页面早已放弃纯服务端渲染SSR但关键数据仍保留在HTML源码中——这是爬虫可行的前提。2024年实测其列表页如https://movie.douban.com/top250?start0filter返回的HTML里每部电影信息包裹在div classitem内而标题、评分、导演等字段均以文本形式嵌入无需执行JavaScript即可提取。但直接用BeautifulSoup解析易受HTML格式化空格干扰且XPath路径容错性差lxml则更轻量、解析更快对标签闭合不严格的情况鲁棒性更强。本节采用requests发请求 lxml.etree解析的组合拒绝Selenium等重量级方案——因为TOP250是静态榜单无滚动加载、无登录态校验、无动态token过度工具化反而掩盖本质问题。2.1 构建抗干扰HTTP请求头与会话管理豆瓣对高频请求有基础频率限制非封IP而是返回403或验证码页但单线程、带合理延时的请求完全可通行。关键在于模拟真实浏览器行为User-Agent必须为近期主流浏览器标识避免使用老旧UA如Mozilla/5.0 (Windows NT 6.1)触发风控Accept-Language需匹配中文环境必须携带Cookie中的ll108288地区语言标记否则部分字段如“推荐”文案会缺失使用requests.Session()复用TCP连接减少握手开销同时便于统一设置headers。import requests from lxml import etree import time import random def create_session(): session requests.Session() headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,image/apng,*/*;q0.8,application/signed-exchange;vb3;q0.7, Connection: keep-alive, Cookie: ll\108288\; } session.headers.update(headers) return session session create_session()提示Cookie中的ll108288是豆瓣中文站固定值非用户登录态可安全复用。若省略此字段部分页面会返回简体中文缺失的乱码字段如导演名显示为amp;#x5B59;amp;#x7ACB;amp;#x514B;而非“孙立克”。2.2 解析HTML并提取250条记录的XPath路径精确定位观察2024年5月豆瓣TOP250第1页HTML结构view-source:https://movie.douban.com/top250?start0filter核心字段位于.item容器内但存在两个关键陷阱标题字段藏在span classtitle中但存在主标题副标题嵌套如《阿凡达》的HTML为span classtitle阿凡达/spannbsp;/nbsp;span classotherAvatar/span需取首个span classtitle的文本忽略nbsp;/nbsp;分隔符评分字段在span classrating_num内但该标签可能为空如新上榜影片暂无评分需做空值判断导演信息在p class的第二行文本中格式为“导演: 张艺谋 主演: 陈道明 / 张国荣”需用正则提取“导演:”后内容且注意nbsp;需替换为普通空格再分割。以下XPath路径经实测稳定截至2024年6月字段XPath路径说明电影标题div[classitem]/div[classpic]/em/text()序号如1主标题div[classitem]/div[classinfo]/div[classhd]/a/span[classtitle][1]/text()取第一个span classtitle排除副标题评分div[classitem]/div[classinfo]/div[classbd]/div[classstar]/span[classrating_num]/text()可能为空字符串需过滤评论数div[classitem]/div[classinfo]/div[classbd]/div[classstar]/span[4]/text()第四个span文本含“人评价”字样需提取数字导演div[classitem]/div[classinfo]/div[classbd]/p[1]/text()[2]p内第二段文本首段为导演/主演第二段常为空格或换行实际需取p[1]的text()[1]并清洗注意p[1]/text()[2]这种写法极易因HTML格式化变动失效。更可靠的做法是获取整个p[1]的text_content()再用正则清洗。本节采用后者确保健壮性。def parse_page(html_content): tree etree.HTML(html_content) items tree.xpath(//div[classitem]) movies [] for item in items: # 序号取em标签文本 rank_elem item.xpath(.//div[classpic]/em) rank rank_elem[0].text.strip() if rank_elem else # 主标题 title_elem item.xpath(.//div[classhd]/a/span[classtitle][1]) title title_elem[0].text.strip() if title_elem else # 评分 rating_elem item.xpath(.//span[classrating_num]) rating rating_elem[0].text.strip() if rating_elem else # 评论数取span[4]文本如“2345678人评价”提取数字 votes_elem item.xpath(.//div[classstar]/span[4]) votes_text votes_elem[0].text.strip() if votes_elem else import re votes re.search(r(\d), votes_text).group(1) if re.search(r(\d), votes_text) else 0 # 导演取p[1]全部文本清洗后提取“导演:”后内容 p1_elem item.xpath(.//div[classbd]/p[1]) p1_text p1_elem[0].text_content().strip() if p1_elem else # 清洗替换nbsp;为空格合并多余空格 p1_clean re.sub(r\s, , p1_text.replace(\xa0, )).strip() # 提取导演匹配“导演: XXX”或“导演:XXX” director_match re.search(r导演[:]\s*(.?)(?:\s主演[:]|$), p1_clean) director director_match.group(1).strip() if director_match else movies.append({ rank: rank, title: title, rating: rating, votes: votes, director: director }) return movies # 测试单页抓取 url https://movie.douban.com/top250?start0filter response session.get(url, timeout10) if response.status_code 200: movies_page1 parse_page(response.text) print(f第1页抓取 {len(movies_page1)} 条记录) print(movies_page1[0]) # 查看第一条 else: print(f请求失败状态码{response.status_code})逻辑说明etree.HTML()将HTML字符串转为可查询树结构比BeautifulSoup快约3倍实测100页解析耗时对比所有XPath均以.//开头表示相对当前item节点查找避免全局搜索冲突text_content()获取节点及其子节点所有文本比单纯text()更可靠应对p导演: a张艺谋/a/p等嵌套正则r导演[:]\s*(.?)(?:\s主演[:]|$)中[:]匹配中文或英文冒号\s*处理空格(.?)非贪婪捕获导演名(?:\s主演[:]|$)为断言确保不跨到主演字段。3. 存储层用SQLite替代CSV用SQLAlchemy ORM建模实现字段约束与增量更新当爬取量达到250条时CSV文件看似简单但很快暴露三大硬伤无数据类型约束评分字段存为字符串“9.7”后续计算需反复float()转换且无法阻止非法值如“9.7a”混入无唯一性保障翻页时若URL参数错位如start25与start50之间漏页同一电影可能被重复采集CSV无法自动去重无事务支持程序中断时CSV可能写到一半导致文件损坏恢复成本高。SQLite是嵌入式数据库零配置、单文件、Python内置支持完美匹配本项目规模。配合SQLAlchemy ORM可将数据模型、字段类型、主键约束、索引全部声明化让存储层具备生产级可靠性。3.1 定义Movie模型与SQLite数据库初始化我们定义Movie表包含6个核心字段id自增主键、rank整型不可为空、title文本唯一约束、rating浮点型允许为空、votes整型、director文本允许为空。其中title设为唯一索引防止同一电影因翻页重复插入。from sqlalchemy import create_engine, Column, Integer, String, Float, Boolean, DateTime from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from datetime import datetime import os Base declarative_base() class Movie(Base): __tablename__ movies id Column(Integer, primary_keyTrue) rank Column(Integer, nullableFalse) title Column(String(200), uniqueTrue, nullableFalse) # 唯一约束防重复 rating Column(Float, nullableTrue) votes Column(Integer, nullableTrue) director Column(String(200), nullableTrue) created_at Column(DateTime, defaultdatetime.now) # 初始化数据库 DB_PATH douban_top250.db engine create_engine(fsqlite:///{DB_PATH}, echoFalse) # echoTrue可查看SQL日志 Base.metadata.create_all(engine) SessionLocal sessionmaker(autocommitFalse, autoflushFalse, bindengine) db_session SessionLocal()参数说明uniqueTrue在title字段上创建唯一索引后续session.add()遇到重复标题会抛IntegrityError可被捕获用于跳过nullableFalse强制rank和title必填避免脏数据入库echoFalse关闭SQL输出提升性能生产环境建议设为False。3.2 实现增量插入捕获重复异常并跳过确保250条数据完整不重不漏全量覆盖式存储先清空再插入看似简单但一旦网络中断250条数据需全部重爬。增量插入则每次只插入新记录已存在则跳过。SQLAlchemy通过捕获sqlalchemy.exc.IntegrityError实现from sqlalchemy.exc import IntegrityError def insert_movies_to_db(movies_list): inserted_count 0 skipped_count 0 for movie in movies_list: try: # 构造Movie实例注意类型转换 db_movie Movie( rankint(movie[rank]), titlemovie[title], ratingfloat(movie[rating]) if movie[rating] else None, votesint(movie[votes]) if movie[votes].isdigit() else 0, directormovie[director] ) db_session.add(db_movie) inserted_count 1 except ValueError as e: print(f数据类型转换错误{movie}错误{e}) continue except IntegrityError as e: # 捕获唯一约束冲突title重复 db_session.rollback() # 回滚本次插入 skipped_count 1 except Exception as e: print(f未知错误{movie}错误{e}) db_session.rollback() db_session.commit() # 批量提交 print(f插入完成成功{inserted_count}条跳过{skipped_count}条重复) # 示例插入第1页数据 insert_movies_to_db(movies_page1)关键设计点db_session.rollback()在捕获IntegrityError后必须调用否则会话处于异常状态后续操作失败int(movie[votes])前用movie[votes].isdigit()预判避免123万类字符串报错float(movie[rating])用if movie[rating] else None处理空评分符合nullableTrue定义。3.3 验证数据完整性用SQL查询检查排名连续性与字段空值率插入完成后不能仅凭print确认成功。需用SQL验证业务规则rank是否为1~250连续整数title是否全部非空rating空值率是否合理豆瓣TOP250中应极少为空def validate_db_data(): # 查询rank范围 result db_session.execute(SELECT MIN(rank), MAX(rank), COUNT(*) FROM movies).fetchone() min_rank, max_rank, total result print(f排名范围{min_rank} ~ {max_rank}总计{total}条) # 检查rank是否连续生成1~250集合对比数据库中rank集合 db_ranks set(r[0] for r in db_session.execute(SELECT rank FROM movies).fetchall()) expected_ranks set(range(1, 251)) missing_ranks expected_ranks - db_ranks if missing_ranks: print(f缺失排名{sorted(missing_ranks)}) else: print(排名完整无缺失) # 统计空值率 null_stats db_session.execute( SELECT COUNT(*) FILTER (WHERE rating IS NULL) * 100.0 / COUNT(*) AS rating_null_pct, COUNT(*) FILTER (WHERE director IS NULL) * 100.0 / COUNT(*) AS director_null_pct FROM movies ).fetchone() print(f评分空值率{null_stats[0]:.2f}%导演空值率{null_stats[1]:.2f}%) validate_db_data()提示COUNT(*) FILTER (WHERE ...)是SQLite 3.30支持的语法若环境版本低可用SUM(CASE WHEN ... THEN 1 ELSE 0 END)替代。本方案默认使用新版SQLitePython 3.11内置。4. 分析层用Pandas做脏数据手术用Seaborn验证分布合理性揪出“玄学”异常值爬下来的数据绝非干净金矿而是裹着泥沙的原石。豆瓣TOP250常见脏数据包括标题错位因XPath误匹配将副标题“/ Avatar”当作主标题评分错位某页span classrating_num被广告位占用导致该位置评分实际属于下一部电影导演字段污染正则未覆盖“导演 / 编剧 / 主演”混合文本提取出“张艺谋 / 刘恒 / 陈道明”评论数失真span文本为“1234567人评价”但实际应为“123.4万”re.search(r(\d)只取前6位。本节用Pandas进行四步清洗字段校验 → 类型规整 → 异常检测 → 业务修正并用可视化交叉验证。4.1 从SQLite加载数据并执行基础字段校验import pandas as pd def load_data_from_db(): # 从SQLite读取为DataFrame df pd.read_sql_query(SELECT * FROM movies ORDER BY rank, engine) print(f原始数据形状{df.shape}) print(df.head(3)) return df df load_data_from_db() # 步骤1校验rank是否为1~250整数 print(\n Rank校验 ) rank_issues df[~df[rank].between(1, 250, inclusiveboth) | ~df[rank].apply(lambda x: isinstance(x, (int, float)))] if not rank_issues.empty: print(Rank异常, rank_issues[[rank, title]].to_dict(records)) # 步骤2校验title长度正常应在2-30字符过短如1或过长如超100字符即异常 print(\n Title长度校验 ) title_len df[title].str.len() len_issues df[(title_len 2) | (title_len 100)] if not len_issues.empty: print(Title长度异常, len_issues[[title, rank]].to_dict(records))4.2 类型规整与空值填充策略rating和votes字段需强制为数值型但直接pd.to_numeric()会将非法值如“”、“—”转为NaN需明确处理策略rating空值保留NaN后续分析时用dropna()或插补votes空值按0处理无评论即0因评论数为计数型字段0有业务意义director空值统一填充为“未知”避免后续str.contains()报错。# 类型规整 df[rating] pd.to_numeric(df[rating], errorscoerce) # 错误转NaN df[votes] pd.to_numeric(df[votes], errorscoerce).fillna(0).astype(int) df[director] df[director].fillna(未知) # 重新检查空值 print(\n 清洗后空值统计 ) print(df.isnull().sum())4.3 异常检测用箱线图与Z-Score揪出“翻车”数据点豆瓣TOP250评分集中在8.0~9.7之间若出现rating1.2或votes99999999远超最大值即为异常。采用双策略箱线图IQR对votes字段计算Q1、Q3定义异常为 Q1-1.5*IQR或 Q31.5*IQRZ-Score对rating字段计算标准分|z| 3视为离群点正态分布假设下3σ外概率仅0.3%。import seaborn as sns import matplotlib.pyplot as plt from scipy import stats # 设置中文字体防止中文乱码 plt.rcParams[font.sans-serif] [SimHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] False # 绘制votes箱线图 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) sns.boxplot(datadf, yvotes) plt.title(评论数箱线图) plt.ylabel(评论数) # 计算IQR异常点 Q1 df[votes].quantile(0.25) Q3 df[votes].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR votes_outliers df[(df[votes] lower_bound) | (df[votes] upper_bound)] print(f\n votes异常值IQR法) print(fIQR范围{lower_bound:.0f} ~ {upper_bound:.0f}) print(votes_outliers[[rank, title, votes]]) # 绘制rating Z-Score散点图 plt.subplot(1, 2, 2) z_scores np.abs(stats.zscore(df[[rating]])) rating_outliers df[z_scores[rating] 3] sns.scatterplot(datadf, xrank, yrating, huez_scores[rating] 3, palette{True: red, False: blue}) plt.title(评分Z-Score分布红色为离群点) plt.xlabel(排名) plt.ylabel(评分) plt.show() print(f\n rating异常值Z-Score法) print(rating_outliers[[rank, title, rating]])血泪经验2024年实测豆瓣TOP250中votes异常多因广告位占位导致错位如第12名数据被写入第11名的votes字段rating异常则多因页面HTML结构微调XPath匹配到错误span。此时不能盲目删除而应人工核对原始HTML源码确认是爬虫逻辑缺陷还是豆瓣数据本身异常。4.4 业务修正基于领域知识修复导演字段与标题歧义导演字段常见问题是提取了“导演 / 编剧”混合内容。根据电影行业常识导演名通常为1~3个汉字如“张艺谋”、“斯蒂文·斯皮尔伯格”超过5个字大概率含编剧中文名间用顿号“、”或“/”分隔英文名用空格若字段含“编剧”、“改编”、“原著”等词应截断至“导演”后、“编剧”前。def clean_director(director_str): if not isinstance(director_str, str) or director_str 未知: return director_str # 移除前后空格和特殊符号 cleaned director_str.strip().replace(\u3000, ).replace(\xa0, ) # 截断“编剧”、“改编”等后续内容 if 编剧 in cleaned: cleaned cleaned.split(编剧)[0] if 改编 in cleaned: cleaned cleaned.split(改编)[0] if 原著 in cleaned: cleaned cleaned.split(原著)[0] # 提取第一个中文姓名匹配汉字、空格、·、-、. # 如“张艺谋、陈凯歌”取“张艺谋”“Steven Spielberg”取全名 name_match re.match(r^([\u4e00-\u9fa5·\-\.\sA-Za-z]?)[\s、/、/], cleaned ) if name_match: return name_match.group(1).strip() else: # 无分隔符取前10字符防超长 return cleaned[:10].strip() df[director_clean] df[director].apply(clean_director) print(\n 导演字段清洗效果 ) print(df[[director, director_clean]].head(5))5. 避坑指南豆瓣TOP250爬虫的5个真实翻车现场与可复现解决方案爬豆瓣TOP250不是技术考试而是与网站演化赛跑的实战。以下5个坑均来自2024年真实调试记录每个都附带现象 → 原因 → 解决的闭环方案拒绝“玄学”归因。5.1 现象XPath匹配到空列表但浏览器审查元素可见目标标签原因豆瓣页面HTML中存在大量!-- --注释块lxml默认将注释视为节点导致xpath(.//div[classitem])实际匹配到注释节点而非div。BeautifulSoup会自动忽略注释但lxml需显式过滤。解决在XPath中添加[not(self::comment())]条件或改用tree.xpath(.//div[classitem and not(self::comment())])。更彻底的方案是解析前移除注释from lxml import etree # 解析前移除注释 parser etree.XMLParser(remove_commentsTrue) tree etree.HTML(html_content, parserparser)5.2 现象第1页正常第2页起返回403 Forbidden原因豆瓣对start参数有校验start25合法但start26会被拦截。必须严格按25的倍数递增0,25,50,...,225且filter参数不可省略。解决构造URL时硬编码步长禁用用户输入for start in range(0, 250, 25): # 0,25,50,...,225 url fhttps://movie.douban.com/top250?start{start}filter5.3 现象导演字段提取出“/”或“ ”等HTML实体原因p[1].text_content()返回的字符串包含未解码的HTML实体如nbsp;、amp;。lxml不自动解码需手动处理。解决用html.unescape()解码import html p1_clean html.unescape(p1_text).replace(\xa0, )5.4 现象CSV导出后Excel打开显示乱码中文变“涓枃”原因Windows系统Excel默认用ANSI编码GBK打开CSV而Pythonto_csv()默认UTF-8无BOM。解决导出时添加BOM头或指定编码为utf-8-sigdf.to_csv(douban_clean.csv, encodingutf-8-sig, indexFalse)5.5 现象rating字段存入SQLite后变为9.699999999999999而非9.7原因浮点数二进制存储精度问题9.7无法精确表示。SQLite的REAL类型即C double同此问题。解决存储前四舍五入到1位小数df[rating] df[rating].round(1) # 9.699... → 9.7注意此操作应在数据分析前完成避免计算误差累积。若需更高精度可存为字符串或整数如97表示9.7。6. 进阶验证用“双盲校验法”交叉验证爬虫数据可信度建立你的数据质量后悔药机制数据可信度不能靠“我觉得没问题”保证而要设计可证伪的验证机制。我给自己定的铁律是任何爬虫产出的数据必须通过至少两种独立来源的交叉验证否则不用于结论。豆瓣TOP250的“双盲校验法”指用爬虫数据与豆瓣官方API如有、第三方权威榜单如IMDb TOP250、或人工抽样三者互验。本节聚焦最可行的人工抽样自动化比对并封装为可一键运行的后悔药脚本。6.1 构建黄金样本集人工核对20部电影的5个核心字段黄金样本是验证基准。我选取TOP250中排名1、10、20、50、100、150、200、220、240、250的10部电影再加10部随机抽取如rank33、77、133等共20部。对每部电影人工访问豆瓣页面记录精确标题区分《》与“”实时评分小数点后1位评论数去除“万”单位转为整数导演仅第一位如“张艺谋”排名确认是否仍在TOP250内。将结果存为gold_sample.csvrank,title,rating,votes,director 1,肖申克的救赎,9.7,2543210,弗兰克·德拉邦特 10,这个杀手不太冷,9.4,1876543,吕克·贝松 ...6.2 自动化比对脚本生成差异报告并定位根因脚本读取爬虫数据库与黄金样本逐字段比对输出diff_report.html高亮差异行并标注可能根因如“XPath错位”、“编码未解码”def generate_diff_report(): # 读取黄金样本 gold_df pd.read_csv(gold_sample.csv) gold_df.set_index(rank, inplaceTrue) # 读取爬虫数据 crawl_df pd.read_sql_query(SELECT rank, title, rating, votes, director FROM movies, engine) crawl_df.set_index(rank, inplaceTrue) # 合并比对 merged gold_df.join(crawl_df, lsuffix_gold, rsuffix_crawl, howinner) # 字段比对逻辑 def compare_field(row, field): gold_val row[f{field}_gold] crawl_val row[f{field}_crawl] if field rating: return abs(float(gold_val) - float(crawl_val)) 0.05 elif field votes: return abs(int(gold_val) - int(crawl_val)) 10000 # 允许1万误差实时波动 else: return str(gold_val).strip() str(crawl_val).strip() # 生成差异表 diff_rows [] for idx, row in merged.iterrows(): title_ok compare_field(row, title) rating_ok compare_field(row, rating) votes_ok compare_field(row, votes) director_ok compare_field(row, director) if not (title_ok and rating_ok and votes_ok and director_ok): diff_rows.append({ rank: idx, title_gold: row[title_gold], title_crawl: row[title_crawl], title_ok: title_ok, rating_gold: row[rating_gold], rating_crawl: row[rating_crawl], rating_ok: rating_ok, votes_gold: row[votes_gold], votes_crawl: row[votes_crawl], votes_ok: votes_ok, director_gold: row[director_gold], director_crawl: row[director_crawl], director_ok: director_ok }) # 输出HTML报告 if diff_rows: diff_df pd.DataFrame(diff_rows) html_report diff_df.to_html(classes p a hrefhttps://download.csdn.net/download/weixin_44087733/89079377 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。