资讯动态

Python+百度API实现微博评论情感分析全链路

发布时间:2026/10/27 18:46:47 来源:尧图企业网站定制
简介围绕百度云API与Python实现的微博评论情感偏向分析这份压缩包面向希望快速上手中文文本情感计算的学习者与课程实践者提供从评论数据到情感得分、再到倾向判定的可运行示例。包内共15个文件以3个Python脚本、6个XML配置、2个XLS表格和1个CSV评论数据集为主另含少量工程说明文件整体约421KB结构紧凑便于按数据、代码、配置模块查阅。已有203人学习下载适合用作课程作业、小论文实验或舆情分析的入门模板。读者可参考其中的微博评论数据理解调用百度API获取文字情感得分的流程并借助标准化脚本把原始得分映射为可比较的实际倾向XML与工程文件则保留了项目配置线索方便复现和二次调整。整体侧重数据与脚本的配套示例能帮助读者快速建立情感分析实验框架并在此基础上扩展品牌口碑、公共事件舆论等场景的验证。1. 三万条评论压在一张表里这条土路为什么还值得走一个做消费品的 A 同学把后台导出的三万条微博评论数据甩过来问能不能当天看出这次投放有没有翻车。人工一条条读不现实逐条问大模型成本顶不住最后落回最土的一条路百度API 做情感计算python 写调用链微博评论数据先洗后打分。单条成本几乎可以忽略三万条一天跑完结果落库还能换口径反复出图。适合手上有几千到几十万条中文短文本、只想要正负向分布的人也适合刚学 python 想跑通一条完整链路的新手。反讽、方言、缩写它认不准但你要的是趋势不是逐条判决。下面按实际跑通的顺序拆返回怎么读、文本怎么洗、调用链怎么写、哪里会翻车。2. 先读懂返回结构情感倾向接口给的四个字段各管什么很多人接上接口就开始跑跑完导出一列sentiment求平均值结果发现正向比例 78%和自己体感完全对不上。问题多半不在模型而在字段被误读了。情感倾向分析接口一次调用返回的东西不多但每个字段的语义边界很清楚先把这层弄清楚后面写聚合逻辑才不会返工。2.1 sentiment 是判决confidence 是判决的把握一次正常返回大致长这样items是数组单条文本就一个元素{ log_id: 1234567890, text: 这次活动力度还行吧, items: [ { sentiment: 1, confidence: 0.52, positive_prob: 0.41, negative_prob: 0.13 } ] }sentiment是离散结论取值 0 负向、1 中性、2 正向。confidence是这个离散结论有多可信注意它不等于正向概率也不等于负向概率它是模型对最终那一类的把握程度。上面这条模型判了中性但把握只有 0.52说明它自己也在犹豫。这种样本后面要单独拎出来看不能直接混进正常样本里算比例。log_id是这次请求的追踪号出问题找客服或者自查日志时用得上建议原样落库别扔。text字段回显的是你传进去的原文如果你传之前做过截断回显的就是截断后的内容可以用来核对到底送进去了什么。提示不同接口版本返回的字段名可能略有差异接之前先在控制台用在线调试工具发一条把真实返回拷下来对照比看文档快。2.2 positive_prob 才是能当强度用的那个positive_prob和negative_prob是两个独立打分加起来通常不等于 1因为中性也占了一部分概率空间。所以拿positive_prob直接当正向强度是错的拿它和negative_prob求比值也不对。真正好用的连续型得分是自己构造的score positive_prob - negative_prob范围在 -1 到 1 之间正数偏正向负数偏负向0 附近是中性。这个值的好处是平滑按天求均值画出来的曲线不会因为模型在临界点上抖动而剧烈跳变。如果你直接用离散的sentiment求平均一条从 1 跳到 2 的样本会让曲线出现台阶看不出趋势。但要清楚这个 score 没有经过概率校准。意思是 score0.6 不代表60% 的概率是正向只代表在这套打分体系里偏正向偏得比较多。做横向对比今天 vs 昨天、这次活动 vs 上次活动没问题做绝对断言67.3% 的用户是正向的就要谨慎最好配合抽样校验一起报。2.3 三条技术路线怎么选做中文短文本情感分析摆在面前的基本就三条路。把成本、可控性、维护量摊开对比选型其实没什么悬念路线单条成本冷启动精度上限维护成本适用场景自训练分类器极低需要几千条标注取决于标注质量高要持续迭代领域固定、量级上千万、有标注人力通用大模型逐条问高无需标注高但输出不稳定中要写 prompt 和校验样本量小、需要归因解释云 NLP 接口低无需标注中等短文本够用极低量大、只要正负向分布三万条这个量级走第三条。真要上千万条再考虑自训练而且自训练的标注集往往还是先用接口跑一遍再人工修等于绕不开这一层。用通用大模型逐条问的问题不在于贵而在于输出格式不稳定你得写解析器兜住各种花式回答反而不省心。这条路线的天花板也要认反讽这波操作我给满分、方言巴适得很、圈层缩写yyds绝绝子基本都会判错或者判成中性低置信。所以不要把单条结果当事实用只把它当统计信号。2.4 接入前的应用创建与密钥管理准备工作不复杂但有两个细节容易埋雷。一是应用要单独创建别和别的项目共用一个 Key否则一个项目把日配额跑光另一个项目当天就瘫了。二是密钥绝对不能写进代码尤其不能提交到代码仓库。我一般用环境变量管理本地开发写进 shell 配置线上走配置中心或者容器环境变量# 写入当前会话重启终端失效长期用请写进 ~/.bashrc 或 ~/.zshrc export BAIDU_NLP_AK你的_API_Key export BAIDU_NLP_SK你的_Secret_Key # 验证是否生效输出应为你的 AK echo $BAIDU_NLP_AKAK是 API KeySK是 Secret Key两个配合换access_token。代码里统一用os.environ[BAIDU_NLP_AK]读取读不到就直接抛异常退出别给一个空字符串兜底否则会以密钥错误的形式在几百条之后才炸排查起来很费劲。控制台里还要确认两件事情感倾向分析这个能力有没有开通当前账号的免费额度和 QPS 限制是多少。QPS 是每秒请求数免费额度是每天的总调用量这两个数字决定了后面限流参数怎么设一定要在控制台页面上亲眼看到别按网上教程里的老数字来配。3. 微博文本清洗把噪音剥掉留下能进接口的那 2000 字节接口对text有字节上限超出直接报参数错误同时微博原文里塞满了话题标签、转发链、表情占位符、零宽字符这些东西送进去只会稀释语义。清洗这一步做得好不好直接决定最终分布可不可信比换个模型影响大得多。3.1 微博文本里的六类噪音先把噪音分类才知道该按什么顺序处理噪音类型典型形态处理方式网页链接https://...、O网页链接整体删除转发链//某某: 原话删除整段重复执行提及某某某删除保留后面正文话题标签#某某话题#去掉井号保留话题文字表情占位符[微笑]、[二哈]删除接口不认控制字符\u200b、\ufeff、私有区图标删除话题标签我建议保留文字而不是整段删掉因为话题词本身往往就是评论的主题比如#新品发布#里的新品发布对判断情感是有信息量的删干净反而丢了上下文。表情占位符则相反[微笑]在中文语境里甚至是反讽信号但接口读不懂方括号里的词留着只会干扰分词删掉更干净。另外还有一类不是噪音但会污染统计的同一条内容被大量转发。转发链清理之后几十条转发会变成几十条一模一样的文本等于给这条内容加了权重。量级大、要做分布统计时建议按clean_text去重后再计数或者至少单独统计重复度。关于数据来源只处理公开可见的内容不采集用户身份、手机号、位置这类信息采集频率压到最低落库时把昵称、主页链接这类字段直接丢掉只留文本和时间。这既是合规底线也让后面的表结构干净很多。3.2 清洗顺序与实现顺序有讲究必须先把 HTML 实体反转义、把控制字符干掉、把链接删掉再去处理转发链和 因为链接里经常夹带和#先删 会把链接切成碎片残留在文本里。转发链要跑两次因为//A: //B: 原文这种嵌套结构一次正则只能剥掉最外层。import html import re RE_URL re.compile(rhttps?://\S|O网页链接|网页链接) RE_REPOST re.compile(r//\s*[^:\s]{1,30}\s*[:]?) RE_AT re.compile(r[A-Za-z0-9_\-\u4e00-\u9fa5]{1,30}) RE_TOPIC re.compile(r#([^#\n]{1,40})#) RE_EMOJI re.compile(r\[[\u4e00-\u9fa5A-Za-z]{1,6}\]) RE_CTRL re.compile(r[\u200b-\u200f\u2060\ufeff\ue000-\uf8ff]) RE_SPACE re.compile(r[ \t\u3000]) def clean(raw: str) - str: 把一条微博原文压成可送进情感接口的纯文本 s html.unescape(raw or ) # 1. 先干掉零宽字符和私有区图标否则后面正则的边界会判错 s RE_CTRL.sub(, s) # 2. 链接优先删链接内部可能带 和 # s RE_URL.sub( , s) # 3. 转发链跑两次处理 //A: //B: 这种嵌套 s RE_REPOST.sub( , s) s RE_REPOST.sub( , s) # 4. 提及删掉正文保留 s RE_AT.sub( , s) # 5. 话题保留文字井号去掉 s RE_TOPIC.sub(r\1, s) # 6. 表情占位符删掉 s RE_EMOJI.sub( , s) # 7. 收尾合并空白、去掉首尾标点 s RE_SPACE.sub( , s) return s.strip( 。,.!?~\n\t)RE_EMOJI里限制 1 到 6 个字符是有意的真人昵称里的方括号内容可能很长限制长度能避免误伤正文。RE_CTRL里的\ue000-\uf8ff是私有使用区微博客户端的一些自定义表情就是渲染成这个区间的字符肉眼看不见但会占字节、干扰分词。3.3 字节截断与无效样本过滤接口按 UTF-8 字节数限长不是字符数。一个汉字 3 字节一条 800 字的微博就是 2400 字节直接超限。所以截断必须按字节做并且要保证截断后不出现半个汉字。def clip_utf8(s: str, max_bytes: int 2000) - str: 按 UTF-8 字节截断尽量收在句读处 b s.encode(utf-8) if len(b) max_bytes: return s # errorsignore 会自动丢掉末尾被切断的半个字符 cut b[:max_bytes].decode(utf-8, errorsignore) # 从后往前找一个句读避免把不没这类否定词和后面的词切开 for ch in (。, , , , , ): idx cut.rfind(ch) if idx 0 and len(cut[:idx].encode(utf-8)) max_bytes // 2: return cut[:idx] return cutmax_bytes取 2000 而不是贴着接口上限是留一点余量给编码差异和后续可能的字段拼接。截断位置优先找句读是为了避免把这个不行切成这个不语义直接反转这种错误在统计里看不出来只能靠截断逻辑提前防住。清洗完之后还要过滤一批无效样本长度小于 4 个字符的哈哈哈转发微博这类、纯标点或纯数字的、清洗后变成空串的。这些送进去只会得到低置信的中性结果白白消耗额度还会把中性比例抬高。过滤规则直接写在入库前的判断里别指望事后补。3.4 落库表结构表结构决定后面能不能断点续跑、能不能回溯。最小可用的设计是两个关键点一是原文和清洗后文本都留出问题时能对比二是状态字段和唯一索引保证重跑不重复、不丢数。-- SQLite 版本MySQL 需把 AUTOINCREMENT 改成 AUTO_INCREMENT CREATE TABLE weibo_comment ( id INTEGER PRIMARY KEY AUTOINCREMENT, comment_id TEXT NOT NULL, -- 平台侧的唯一标识 batch_tag TEXT NOT NULL, -- 采集批次区分不同次抓取 raw_text TEXT NOT NULL, -- 原始文本回溯用 clean_text TEXT NOT NULL, -- 清洗后文本送接口的就是它 publish_time TEXT, -- 发布时间聚合按天要用 like_count INTEGER DEFAULT 0, -- 点赞数可做加权 status INTEGER DEFAULT 0, -- 0待处理 1成功 2参数错 3异常 sentiment INTEGER, -- 0负向 1中性 2正向 confidence REAL, positive_prob REAL, negative_prob REAL, log_id TEXT, err_msg TEXT, updated_at TEXT ); CREATE UNIQUE INDEX uk_comment ON weibo_comment(batch_tag, comment_id); CREATE INDEX idx_pending ON weibo_comment(batch_tag, status, id);唯一索引建在(batch_tag, comment_id)上而不是单独的comment_id因为同一条微博可能在不同批次被重复采到你要允许它存在两次但不能在同一个批次里重复。idx_pending是给取待处理数据用的status0的扫描会走这个索引几十万行也不会慢。like_count建议留着。后面做加权聚合时一条一万赞的评论和一条零赞的评论权重显然不该一样但怎么加权是业务判断先把数据存下来口径可以后面再换。4. 调用链落地token 缓存、限流退避与断点续跑清洗好的数据要一条条送出去。这一段最容易出问题的地方不是调用本身而是 token 管理、限流节奏和失败恢复。写得糙一点跑三万条能把你折腾一整天。4.1 access_token 的缓存与刷新access_token是拿 AK/SK 换的有效期通常按天算。最常见的错误写法是每处理一条就换一次 token请求量直接翻倍还没开始调接口就把 QPS 打满了然后满屏的错误码。正确做法是进程内缓存用一个带锁的类管住临近过期才刷新import os import time import threading import requests TOKEN_URL https://aip.baidubce.com/oauth/2.0/token class TokenKeeper: 线程安全的 token 缓存提前一天刷新 def __init__(self, ak: str, sk: str, refresh_ahead: int 86400): self.ak ak self.sk sk self.refresh_ahead refresh_ahead # 提前刷新的秒数 self._token None self._expire_at 0.0 self._lock threading.Lock() def get(self, force: bool False) - str: with self._lock: now time.time() if not force and self._token and now self._expire_at: return self._token resp requests.get(TOKEN_URL, params{ grant_type: client_credentials, client_id: self.ak, client_secret: self.sk, }, timeout10) resp.raise_for_status() data resp.json() if access_token not in data: raise RuntimeError(f取 token 失败: {data}) self._token data[access_token] # expires_in 单位秒提前 refresh_ahead 秒视为过期 self._expire_at now data[expires_in] - self.refresh_ahead return self._token三个参数值得说refresh_ahead设成 86400一天意味着在真正过期前一天就换新的避免跑到一半 token 失效force参数是给错误码 110/111 用的遇到 token 失效时强制刷新一次再重试_lock保证多线程下只有一个线程去换 token其他线程等结果不会出现并发换 token 把配额打光。AK/SK 从环境变量读别硬编码keeper TokenKeeper( akos.environ[BAIDU_NLP_AK], skos.environ[BAIDU_NLP_SK], )4.2 单条调用的封装与错误码分流接口请求本身简单难在错误码怎么分类。我的习惯是分四档可重试的、要换 token 的、配额用尽的、参数错的。四档处理方式完全不同混在一起写就会出现参数错误也重试四次这种浪费。import random import requests SENTIMENT_URL https://aip.baidubce.com/rpc/2.0/nlp/v1/sentiment_classify RETRYABLE {18, 282000, 282810} # QPS 超限、服务内部错误等 TOKEN_INVALID {110, 111} # token 无效 / 过期 QUOTA_EXHAUSTED {17, 19} # 日配额或权限用尽 def _call_once(text: str, token: str, timeout: int 8) - dict: resp requests.post( SENTIMENT_URL, params{access_token: token, charset: UTF-8}, json{text: text}, # 用 json 让 requests 自行处理 UTF-8 timeouttimeout, ) resp.raise_for_status() return resp.json() def analyze(text: str, keeper: TokenKeeper, max_retry: int 4) - dict: 返回 items[0]并把 log_id 塞进去方便落库 for attempt in range(max_retry 1): token keeper.get() try: data _call_once(text, token) except requests.RequestException: # 网络层错误退避后重试 if attempt max_retry: raise time.sleep(min(2 ** attempt random.random(), 30)) continue code data.get(error_code, 0) if code 0: item data[items][0] item[log_id] data.get(log_id, ) return item if code in TOKEN_INVALID: keeper.get(forceTrue) # 强制换 token不消耗重试次数 continue if code in RETRYABLE: time.sleep(min(2 ** attempt random.random(), 30)) continue if code in QUOTA_EXHAUSTED: raise RuntimeError(f配额用尽停止: {data}) # 参数类错误不重试交给上层标记跳过 raise ValueError(f不可重试错误 {code}: {data.get(error_msg)}) raise RuntimeError(重试次数耗尽)json{text: text}这个写法比datajson.dumps({text: text})更稳因为 requests 会自己把Content-Type设成application/json并做 UTF-8 编码省掉一类编码问题。退避用的是2 ** attempt 随机数最大封顶 30 秒随机数是为了避免多个线程同时重试形成脉冲。QUOTA_EXHAUSTED直接抛出中断整个任务不要吞掉继续跑。配额用尽之后每一条都会失败继续跑只是往库里写一堆无意义的错误记录还会把状态字段搞乱。4.3 并发控制QPS 从 1 开始压免费额度的 QPS 通常很低一上来开十个线程必然满屏错误码 18。稳妥的做法是单线程匀速跑或者用线程池加信号量把并发压住。我的习惯是先按 1 QPS 跑一百条看有没有错误再逐步往上加最终稳定在控制台标注值的 0.8 倍左右。单线程匀速版本最省心也最不容易翻车import time def run_batch(conn, keeper, batch_tag, qps1.5, page_size200, limitNone): 从 status0 的记录里取数逐条调用并回写。Ctrl-C 后重跑可续 interval 1.0 / qps done 0 while True: rows conn.execute( SELECT id, clean_text FROM weibo_comment WHERE status 0 AND batch_tag ? ORDER BY id LIMIT ? , (batch_tag, page_size)).fetchall() if not rows: break for row in rows: t0 time.time() try: item analyze(row[clean_text], keeper) conn.execute( UPDATE weibo_comment SET status 1, sentiment ?, confidence ?, positive_prob ?, negative_prob ?, log_id ?, updated_at datetime(now) WHERE id ? , (item[sentiment], item[confidence], item[positive_prob], item[negative_prob], item.get(log_id, ), row[id])) except ValueError as e: # 参数类错误标记跳过不阻塞后续 conn.execute( UPDATE weibo_comment SET status2, err_msg? WHERE id?, (str(e)[:200], row[id])) except Exception as e: # 网络/未知异常标记 status3下一轮人工或脚本重试 conn.execute( UPDATE weibo_comment SET status3, err_msg? WHERE id?, (str(e)[:200], row[id])) conn.commit() done 1 cost time.time() - t0 if cost interval: time.sleep(interval - cost) if limit and done limit: return done return done参数说明qps1.5是每秒请求数第一次跑务必从 1 开始page_size200是每批取多少行取太大内存占得多、也更容易在某条失败时丢掉进度limit用来做小样本试跑传 100 就只跑一百条验证通了再放开。conn.commit()放在单条循环内而不是每批结束看起来费但这是断点续跑的关键任何时候 Ctrl-C 或者进程被杀已完成的记录都已落库重跑时status0的查询自然跳过它们。用 SQLite 时这个提交开销可以忽略MySQL 的话建议改成每 50 条提交一次。4.4 断点续跑与失败重试上面这套状态机是 0 待处理、1 成功、2 参数错、3 异常。重试只需要把 3 拨回 0-- 把异常记录重新放回待处理队列参数错的不要动 UPDATE weibo_comment SET status 0, retry_count retry_count 1 WHERE batch_tag ? AND status 3 AND retry_count 3;retry_count 3是个闸门防止某条数据因为特殊字符一直失败被反复送进队列形成死循环。跑够三次还失败的多半是真的有脏数据人工捞出来看一眼比继续重试划算。跑完一批之后一定要对账三个数必须能对上SELECT status, COUNT(*) FROM weibo_comment WHERE batch_tag ? GROUP BY status;status1的数量加上 2 和 3 的数量应该等于status非空的全部行数。如果对不上说明有记录根本没被读到多半是batch_tag写错了或者清洗阶段就没入库。5. 踩坑排查五个把结果带偏的细节这一段是我自己在不同项目里反复撞过的墙按现象、原因、解决写遇到对得上的直接照方抓药。5.1 中性比例高得离谱正向负向都很少现象跑完十万条中性占 71%正向 16%负向 13%但业务侧反馈这次明明骂声一片。原因清洗过度很多评论清洗完只剩几个字甚至空串或者残留的[微笑]这类占位符还在。短到还行不错这种两三个字的样本接口本身就容易判中性低置信。解决先抽样看clean_text随机拉 50 条肉眼过一遍看看是不是被削得太狠。把最小长度过滤从 4 调到 3 或者干脆不过滤同时把confidence 0.55的样本单独统计成一个判不准桶不要混进中性里。这一步做完中性比例通常会降 10 个百分点以上。5.2 跑到几百条后全部失败错误码 18现象前 300 条正常之后每条都返回 QPS 超限重试也没用越重试越糟。原因两个可能叠加。一是 token 每条都重新取请求量翻倍二是重试没有退避失败后立刻重发把本来就紧张的配额彻底打满。解决确认TokenKeeper真的在做缓存打印一下_expire_at看有没有变重试必须带指数退避把 QPS 降到 1 重新跑。如果控制台显示的是共享配额还要确认同账号下没有别的程序在跑。5.3 中断重跑后数据翻倍现象脚本跑到一半断了重跑一遍最后统计出来的总量是预期的一点五倍。原因表上没有唯一约束重复插入或者采集阶段就没去重同一条微博被多个批次抓进来而聚合时没按batch_tag过滤。解决唯一索引建在(batch_tag, comment_id)上插入用INSERT OR IGNOREMySQL 用INSERT IGNORE。聚合查询永远带batch_tag条件别图省事全表扫。5.4 超长微博被判成完全相反的情感现象一条长评论原文明显是差评结果判成中性甚至正向。原因截断截在了句子中间把但是质量真的不行这后半句切掉了只留下前面的客套话或者按字符数截断UTF-8 字节超限接口只处理了前面一小段。解决按字节截断并且从后往前找句读落点对于超过 1500 字节的长文本可以在截断前先做一次分句取情感词密度最高的那一段送进去。这条没有银弹长文本本身就是这类接口的弱项报告里要注明长文本占比。5.5 分布和体感差很远但代码没报错现象全流程跑通没有一条报错但结果就是和运营的直觉对不上。原因样本偏差。常见的是只采了热门评论排序靠前的天然争议大、只采了带图的图文内容情感更极端、或者只采了某个时间段活动刚爆发那两小时。代码没错错在采样。解决采集阶段就按时间分层每小时抽固定条数跑完先做一次抽样校验下一章讲报告里必须写清楚样本口径——采了多少条、去重后多少条、时间范围、过滤掉了哪些。口径写清楚即使数字不好看结论也是站得住的。6. 从分数到结论阈值标定与两百条抽样校验接口跑通只是拿到原料能不能出结论取决于你敢不敢给结果标一个误差范围。我现在的固定动作是先标阈值再抽样校验最后才出图。阈值标定解决的是哪些样本算数。把confidence分层统计你会发现低置信段几乎全是短文本、反讽和圈层黑话。我的做法是设一条线比如confidence 0.6记为可靠样本低于这条线的单独归入判不准桶只报比例不报倾向。这个桶本身很有价值——判不准的比例突然升高往往说明这波讨论里反讽和阴阳怪气变多了这比正负向比例更能说明舆情在变味。抽样校验解决的是结果准不准。从可靠样本里随机抽 200 条人工标注正负中和接口结果做混淆矩阵。200 条是个经验值少于 100 条置信区间太宽多于 500 条人工成本吃不消。下面的数字是示意不是标准答案你要用自己的数据重新跑分层样本数人工一致率主要错法confidence ≥ 0.81200.91反讽被当正向0.6 ≤ confidence 0.8600.78中性被判成正/负confidence 0.6200.55基本随机这张表出来之后报告里就可以写在置信度 0.8 以上的样本中人工复核一致率 91%比单说一个准确率 85%可信得多。低于 0.6 那档一致率接近抛硬币就别往结论里掺了。聚合口径我用加权得分按天算# score 落在 [-1, 1]正数偏正向按天求均值再画曲线 df[score] df[positive_prob] - df[negative_prob] daily (df[df[confidence] 0.6] .groupby(df[publish_time].str[:10])[score] .agg([mean, count]) .rename(columns{mean: avg_score, count: n})) # 样本量太小的天不要画否则曲线尖刺全是噪音 daily daily[daily[n] 30]n 30这个门槛是经验值一天的样本少于 30 条均值波动太大画上去只会让人误判。真要看那几天就在图上标出来说明样本不足别硬画。一个具体技巧把判不准比例和平均分画在同一张图的双轴上。平均分告诉你情绪往哪边走判不准比例告诉你这个判断有多虚。两条线同时抬头往往意味着争议在扩大而不是单纯的负面增加。血泪经验是有一回赶时间三万条跑完直接出了张正向占比 82%的图会上被问了一句你抽了几条看当场卡住。后来补抽 200 条发现大量反讽被算成正向真实正向不到 60%。从那以后我每次跑完都先抽 30 条肉眼过一遍再出图这一步花不了十分钟但能省掉一次被打脸的会。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑