account_position_info.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. import asyncio
  2. import traceback
  3. import numpy as np
  4. from collections import defaultdict
  5. from typing import Dict, List, Set
  6. from pandas import DataFrame
  7. from scipy import stats
  8. from tqdm.asyncio import tqdm
  9. from datetime import datetime, timedelta
  10. from app.infra.external.apollo import AsyncApolloApi
  11. class AccountPositionInfoConst:
  12. # 阅读率统计周期(秒)
  13. STATISTICS_PERIOD = 31 * 24 * 60 * 60
  14. # 一天的秒数
  15. ONE_DAY_IN_SECONDS = 60 * 60 * 24
  16. # 相对变化率阈值
  17. RELATIVE_VALUE_THRESHOLD = 0.1
  18. # 发文类型
  19. UNLIMITED_PUBLISH_TYPE = 10002
  20. BULK_PUBLISH_TYPE = 9
  21. # 文章位置
  22. ARTICLE_INDEX_LIST = [1, 2, 3, 4, 5, 6, 7, 8]
  23. # 默认粉丝
  24. DEFAULT_FANS = 0
  25. # 最低粉丝量
  26. MIN_FANS = 1000
  27. ARTICLES_DAILY = 1
  28. TOULIU = 2
  29. # 统计周期(天)
  30. STAT_PERIOD = 30
  31. # 默认点赞
  32. DEFAULT_LIKE = 0
  33. # 状态
  34. USING_STATUS = 1
  35. NOT_USING_STATUS = 0
  36. PUBLISH_SUCCESS_STATUS = 2
  37. # 不再使用的服务号
  38. NOT_USED_SERVER_ACCOUNT = {"gh_84e744b16b3a", "gh_5855bed97938", "gh_61a72b720de3"}
  39. # Apollo 挂掉时的封禁账号兜底列表
  40. FORBIDDEN_GH_IDS_FALLBACK = {
  41. "gh_bfe5b705324a",
  42. "gh_183d80deffb8",
  43. "gh_789a40fe7935",
  44. "gh_3ed305b5817f",
  45. "gh_043223059726",
  46. "gh_9877c8541764",
  47. "gh_341e08f852f5",
  48. "gh_9eef14ad6c16",
  49. "gh_93e00e187787",
  50. "gh_e0eb490115f5",
  51. "gh_1686250f15b6",
  52. "gh_62d7f423f382",
  53. "gh_4568b5a7e2fe",
  54. "gh_98ec0ffe69b3",
  55. "gh_68e7fdc09fe4",
  56. "gh_b3b10dc6410e",
  57. "gh_cd041ed721e6",
  58. "gh_d4dffc34ac39",
  59. "gh_bff0bcb0694a",
  60. "gh_e24da99dc899",
  61. "gh_a51201bcff28",
  62. "gh_4c058673c07e",
  63. "gh_5ae65db96cb7",
  64. "gh_b1c71a0e7a85",
  65. "gh_ac43eb24376d",
  66. "gh_ff487cb5dab3",
  67. "gh_a2901d34f75b",
  68. "gh_b181786a6c8c",
  69. "gh_7c66e0dbd2cf",
  70. "gh_5ff48e9fb9ef",
  71. "gh_be8c29139989",
  72. "gh_c91b42649690",
  73. "gh_72bace6b3059",
  74. "gh_f902cea89e48",
  75. "gh_6b7c2a257263",
  76. "gh_dd4c857bbb36",
  77. "gh_c69776baf2cd",
  78. "gh_d5f935d0d1f2",
  79. "gh_c5cdf60d9ab4",
  80. "gh_6d9f36e3a7be",
  81. "gh_7f5075624a50",
  82. "gh_56ca3dae948c",
  83. "gh_03d45c260115",
  84. "gh_ac43e43b253b",
  85. "gh_adca24a8f429",
  86. "gh_6cfd1132df94",
  87. "gh_77f36c109fb1",
  88. "gh_080bb43aa0dc",
  89. "gh_660afe87b6fd",
  90. "gh_7b4a5f86d68c",
  91. "gh_968bb63520bb",
  92. "gh_0e36c1cd92ab",
  93. "gh_de9f9ebc976b",
  94. "gh_b15de7c99912",
  95. "gh_9f8dc5b0c74e",
  96. "gh_7e5818b2dd83",
  97. "gh_0c89e11f8bf3",
  98. }
  99. # 投流账号
  100. TOULIU_ACCOUNTS = {
  101. "小阳看天下",
  102. "趣味生活方式",
  103. "趣味生活漫时光",
  104. "史趣探秘",
  105. "暖心一隅",
  106. "趣味生活漫谈",
  107. "历史长河流淌",
  108. "美好意义时光",
  109. "银发生活畅谈",
  110. "美好时光阅读汇",
  111. "时光趣味生活",
  112. "生活慢时光",
  113. }
  114. class AccountPositionReadRateAvg(AccountPositionInfoConst):
  115. """计算账号每个位置评价阅读率"""
  116. def __init__(self, pool, log_client, trace_id, apollo_config=None):
  117. self.pool = pool
  118. self.log_client = log_client
  119. self.trace_id = trace_id
  120. self.apollo_client = (
  121. AsyncApolloApi(apollo_config=apollo_config, app_id=None, env="prod")
  122. if apollo_config
  123. else None
  124. )
  125. async def _get_forbidden_gh_ids(self) -> set:
  126. """从 Apollo 获取封禁账号列表,失败时兜底使用硬编码列表"""
  127. if not self.apollo_client:
  128. return self.FORBIDDEN_GH_IDS_FALLBACK
  129. try:
  130. ids = await self.apollo_client.get_config_value(key="forbidden_gh_ids")
  131. return set(ids)
  132. except Exception:
  133. return self.FORBIDDEN_GH_IDS_FALLBACK
  134. # 生成统计周期
  135. def generate_stat_duration(self, end_date: str) -> str:
  136. end_date_dt = datetime.strptime(end_date, "%Y-%m-%d")
  137. start_date_dt = end_date_dt - timedelta(seconds=self.STATISTICS_PERIOD)
  138. return start_date_dt.strftime("%Y-%m-%d")
  139. # 获取发文账号
  140. async def get_publishing_accounts(self):
  141. query = """
  142. select distinct
  143. t3.name as account_name,
  144. t3.gh_id as gh_id,
  145. group_concat(distinct t4.remark) as account_remark,
  146. t6.account_source_name as account_source,
  147. t6.mode_type as mode_type,
  148. t6.account_type as account_type,
  149. t6.`status` as status
  150. from
  151. publish_plan t1
  152. join publish_plan_account t2 on t1.id = t2.plan_id
  153. join publish_account t3 on t2.account_id = t3.id
  154. left join publish_account_remark t4 on t3.id = t4.publish_account_id
  155. left join wx_statistics_group_source_account t5 on t3.id = t5.account_id
  156. left join wx_statistics_group_source t6 on t5.group_source_name = t6.account_source_name
  157. where t1.plan_status = 1 and t1.content_modal = 3 and t3.channel = 5
  158. group by t3.id;
  159. """
  160. account_list = await self.pool.async_fetch(query, db_name="aigc")
  161. return [i for i in account_list if "自动回复" not in str(i["account_remark"])]
  162. # 获取服务号分组发文信息
  163. async def get_server_group_publish_accounts(self) -> Set[str]:
  164. query = """
  165. select gzh_id from article_gzh_developer;
  166. """
  167. fetch_response = await self.pool.async_fetch(
  168. query=query, db_name="piaoquan_crawler"
  169. )
  170. gh_id_list = [
  171. i["gzh_id"]
  172. for i in fetch_response
  173. if i["gzh_id"] not in self.NOT_USED_SERVER_ACCOUNT
  174. ]
  175. return set(gh_id_list)
  176. # 获取统计周期内,每个账号的粉丝量
  177. async def get_fans_for_each_date(self, start_date: str):
  178. # 获取订阅号粉丝量
  179. query = """
  180. SELECT t1.date_str as dt,
  181. CASE
  182. WHEN t1.fans_count IS NULL OR t1.fans_count = 0 THEN t2.follower_count
  183. ELSE t1.fans_count
  184. END AS fans,
  185. t2.gh_id as gh_id
  186. FROM datastat_wx t1 JOIN publish_account t2 ON t1.account_id = t2.id
  187. WHERE t2.channel = 5 AND t2.status = 1 AND t1.date_str >= %s;
  188. """
  189. task1 = self.pool.async_fetch(query=query, db_name="aigc", params=(start_date,))
  190. group_account_set = await self.get_server_group_publish_accounts()
  191. if group_account_set:
  192. query_group = f"""
  193. select publish_date as dt, gh_id, account_name, CAST(SUM(total_sent_fans) AS SIGNED) AS fans
  194. from (
  195. select publish_date, account_name, gh_id, push_id, avg(sent_count) as 'total_sent_fans'
  196. from long_articles_group_send_result
  197. where publish_date >= %s and status = %s
  198. group by publish_date, account_name, push_id
  199. ) as lagsr
  200. group by lagsr.publish_date, gh_id;
  201. """
  202. params_group = (start_date, self.PUBLISH_SUCCESS_STATUS)
  203. task2 = self.pool.async_fetch(query=query_group, params=params_group)
  204. else:
  205. # 没有 group 账号,返回空列表
  206. task2 = asyncio.sleep(0, result=[])
  207. account_with_fans, group_account_with_fans = await asyncio.gather(task1, task2)
  208. # 合并粉丝数据
  209. account_dt_fans_mapper: Dict[str, Dict[str, int]] = defaultdict(dict)
  210. # 订阅号
  211. for item in account_with_fans or []:
  212. gh_id = item["gh_id"]
  213. dt = item["dt"]
  214. fans = int(item.get("fans") or 0)
  215. account_dt_fans_mapper[gh_id][dt] = fans
  216. # 服务号(覆盖相同 gh_id + dt)
  217. for item in group_account_with_fans or []:
  218. gh_id = item["gh_id"]
  219. dt = item["dt"]
  220. fans = int(item.get("fans") or 0)
  221. account_dt_fans_mapper[gh_id][dt] = fans
  222. return account_dt_fans_mapper
  223. # 从数据库获取账号群发文章 && 群发数据
  224. async def get_single_account_published_articles(
  225. self, gh_id: str, start_timestamp: int
  226. ):
  227. query = """
  228. SELECT
  229. ghId as gh_id, accountName as account_name,
  230. ItemIndex as position,
  231. CAST(AVG(show_view_count) AS SIGNED) as read_count,
  232. FROM_UNIXTIME(publish_timestamp, '%%Y-%%m-%%d') AS pub_dt
  233. FROM
  234. official_articles_v2
  235. WHERE
  236. ghId = %s and Type = %s and publish_timestamp >= %s
  237. GROUP BY ghId, accountName, ItemIndex, pub_dt;
  238. """
  239. return await self.pool.async_fetch(
  240. query=query,
  241. db_name="piaoquan_crawler",
  242. params=(gh_id, self.BULK_PUBLISH_TYPE, start_timestamp),
  243. )
  244. # 计算单个账号的每篇文章的阅读率
  245. async def cal_read_rate_for_single_account(
  246. self,
  247. publish_details: List[Dict],
  248. gh_id: str,
  249. fans_mapper: Dict[str, Dict[str, int]],
  250. ) -> DataFrame | None:
  251. if not publish_details:
  252. return None
  253. article_list_with_fans = []
  254. for article in publish_details:
  255. fans = fans_mapper.get(gh_id, {}).get(article["pub_dt"], self.DEFAULT_FANS)
  256. if not fans:
  257. print(
  258. f"账号 {article['account_name']} 在 {article['pub_dt']} 没有粉丝数据"
  259. )
  260. continue
  261. article["fans"] = fans
  262. if fans > self.MIN_FANS:
  263. article["read_rate"] = article["read_count"] / fans if fans else 0
  264. article_list_with_fans.append(article)
  265. # 转化为 DataFrame 方便后续处理
  266. return DataFrame(
  267. article_list_with_fans,
  268. columns=[
  269. "gh_id",
  270. "account_name",
  271. "position",
  272. "read_count",
  273. "pub_dt",
  274. "fans",
  275. "read_rate",
  276. ],
  277. )
  278. # 更新账号阅读率均值并且更新数据库
  279. async def update_read_rate_avg_for_each_account(
  280. self,
  281. account: dict,
  282. start_date: str,
  283. end_dt: str,
  284. df: DataFrame,
  285. fans_dict: Dict[str, int],
  286. ):
  287. avg_date = (datetime.strptime(end_dt, "%Y-%m-%d") - timedelta(days=1)).strftime(
  288. "%Y-%m-%d"
  289. )
  290. insert_error_list = []
  291. for index in self.ARTICLE_INDEX_LIST:
  292. # 过滤
  293. filter_df = df[
  294. (df["position"] == index)
  295. & (df["pub_dt"] < end_dt)
  296. & (df["pub_dt"] >= start_date)
  297. ]
  298. read_average = filter_df["read_count"].mean()
  299. read_std = filter_df["read_count"].std()
  300. output_df = filter_df[
  301. (filter_df["read_count"] > read_average - 2 * read_std)
  302. & (filter_df["read_count"] < read_average + 2 * read_std)
  303. ]
  304. records = len(output_df)
  305. if records:
  306. # todo: 需要检查波动
  307. # if index <= 2:
  308. # print("position need to be checked")
  309. # insert
  310. try:
  311. insert_query = """
  312. INSERT INTO long_articles_read_rate
  313. (account_name, gh_id, position, read_rate_avg, remark, articles_count, earliest_publish_time, latest_publish_time, dt_version, is_delete, fans)
  314. VALUES
  315. (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
  316. """
  317. await self.pool.async_save(
  318. query=insert_query,
  319. params=(
  320. account["account_name"],
  321. account["gh_id"],
  322. index,
  323. output_df["read_rate"].mean(),
  324. "从 {} 开始往前计算 31 天".format(start_date),
  325. records,
  326. output_df["pub_dt"].min(),
  327. output_df["pub_dt"].max(),
  328. avg_date.replace("-", ""),
  329. 0,
  330. fans_dict.get(avg_date, 0),
  331. ),
  332. )
  333. except Exception as e:
  334. insert_error_list.append(str(e))
  335. # 入口函数
  336. async def deal(self, end_date: str | None):
  337. if not end_date:
  338. end_date = datetime.now().strftime("%Y-%m-%d")
  339. start_dt = self.generate_stat_duration(end_date)
  340. fans_mapper = await self.get_fans_for_each_date(start_date=start_dt)
  341. accounts = await self.get_publishing_accounts()
  342. forbidden_gh_ids = await self._get_forbidden_gh_ids()
  343. for account in tqdm(accounts, desc="计算单个账号阅读率均值"):
  344. if account["gh_id"] in forbidden_gh_ids:
  345. continue
  346. published_articles = await self.get_single_account_published_articles(
  347. gh_id=account["gh_id"],
  348. start_timestamp=int(
  349. datetime.strptime(start_dt, "%Y-%m-%d").timestamp()
  350. ),
  351. )
  352. article_dataframe = await self.cal_read_rate_for_single_account(
  353. publish_details=published_articles,
  354. gh_id=account["gh_id"],
  355. fans_mapper=fans_mapper,
  356. )
  357. if article_dataframe is None:
  358. continue
  359. if article_dataframe.empty:
  360. continue
  361. await self.update_read_rate_avg_for_each_account(
  362. account=account,
  363. start_date=start_dt,
  364. end_dt=end_date,
  365. df=article_dataframe,
  366. fans_dict=fans_mapper.get(account["gh_id"], {}),
  367. )
  368. class AccountPositionReadAvg(AccountPositionReadRateAvg):
  369. # 计算阅读均值置信区间上限
  370. async def cal_read_avg_ci_upper(self, gh_id: str, index: int):
  371. fetch_query = f"""
  372. select read_avg, update_time
  373. from account_avg_info_v3
  374. where gh_id = %s and position = %s
  375. order by update_time desc limit 30;
  376. """
  377. fetch_response_list = await self.pool.async_fetch(
  378. query=fetch_query, db_name="piaoquan_crawler", params=(gh_id, index)
  379. )
  380. read_avg_list = [
  381. i["read_avg"] for i in fetch_response_list if i["read_avg"] is not None
  382. ]
  383. n = len(read_avg_list)
  384. mean = np.mean(read_avg_list)
  385. std = np.std(read_avg_list, ddof=1)
  386. se = std / np.sqrt(n)
  387. t = stats.t.ppf(0.975, df=n - 1)
  388. upper_t = mean + t * se
  389. return upper_t
  390. # 获取账号的阅读率均值信息
  391. async def get_accounts_read_avg(self, dt):
  392. query = """
  393. select gh_id, position, fans, read_rate_avg, fans * read_rate_avg as read_avg
  394. from long_articles_read_rate
  395. where dt_version = %s
  396. """
  397. fetch_result = await self.pool.async_fetch(
  398. query=query, params=(dt.replace("-", ""),)
  399. )
  400. response = {}
  401. for item in fetch_result:
  402. key = f"{item['gh_id']}_{item['position']}"
  403. response[key] = {
  404. "read_rate_avg": item["read_rate_avg"],
  405. "read_avg": item["read_avg"],
  406. "fans": item["fans"],
  407. }
  408. return response
  409. # 计算阅读均值置信区间上限
  410. async def cal_read_avg_detail(
  411. self, account: Dict, dt: str, account_with_read_rate_avg: Dict
  412. ):
  413. for index in self.ARTICLE_INDEX_LIST:
  414. key = f"{account['gh_id']}_{index}"
  415. print(key)
  416. if account_with_read_rate_avg.get(key) is None:
  417. continue
  418. read_avg = account_with_read_rate_avg[key]["read_avg"]
  419. # 计算阅读均值置信区间上限
  420. read_avg_ci_upper = await self.cal_read_avg_ci_upper(
  421. gh_id=account["gh_id"], index=index
  422. )
  423. await self.process_each_record(
  424. account=account,
  425. index=index,
  426. fans=account_with_read_rate_avg[key]["fans"],
  427. read_rate_avg=account_with_read_rate_avg[key]["read_rate_avg"],
  428. read_avg=read_avg,
  429. read_avg_ci_upper=read_avg_ci_upper,
  430. dt=dt,
  431. )
  432. async def process_each_record(
  433. self, account, index, fans, read_rate_avg, read_avg, read_avg_ci_upper, dt
  434. ):
  435. gh_id = account["gh_id"]
  436. account_name = account["account_name"]
  437. business_type = (
  438. self.TOULIU if account_name in self.TOULIU_ACCOUNTS else self.ARTICLES_DAILY
  439. )
  440. # insert into database
  441. insert_sql = f"""
  442. insert into account_avg_info_v3
  443. (gh_id, position, update_time, account_name, fans, read_avg, like_avg, status, account_type,
  444. account_mode, account_source, account_status, business_type, read_rate_avg, read_avg_ci_upper)
  445. values
  446. (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s);
  447. """
  448. try:
  449. await self.pool.async_save(
  450. query=insert_sql,
  451. db_name="piaoquan_crawler",
  452. params=(
  453. gh_id,
  454. index,
  455. dt,
  456. account["account_name"],
  457. fans,
  458. read_avg,
  459. self.DEFAULT_LIKE,
  460. self.USING_STATUS,
  461. account["account_type"],
  462. account["mode_type"],
  463. account["account_source"],
  464. account["status"],
  465. business_type,
  466. read_rate_avg,
  467. read_avg_ci_upper,
  468. ),
  469. )
  470. except Exception as e:
  471. print(e)
  472. update_sql = f"""
  473. update account_avg_info_v3
  474. set fans = %s, read_avg = %s, read_rate_avg = %s, read_avg_ci_upper = %s
  475. where gh_id = %s and position = %s and update_time = %s
  476. """
  477. try:
  478. await self.pool.async_save(
  479. query=update_sql,
  480. db_name="piaoquan_crawler",
  481. params=(
  482. fans,
  483. read_avg,
  484. read_rate_avg,
  485. read_avg_ci_upper,
  486. account["gh_id"],
  487. index,
  488. dt,
  489. ),
  490. )
  491. except Exception as e:
  492. print(e)
  493. # 修改前一天的状态为 0
  494. update_status_sql = f"""
  495. UPDATE account_avg_info_v3
  496. SET status = %s
  497. WHERE update_time != %s AND gh_id = %s AND position = %s;
  498. """
  499. await self.pool.async_save(
  500. query=update_status_sql,
  501. db_name="piaoquan_crawler",
  502. params=(self.NOT_USING_STATUS, dt, gh_id, index),
  503. )
  504. async def deal(self, end_date: str | None):
  505. if not end_date:
  506. end_date = datetime.now().strftime("%Y-%m-%d")
  507. dt = (datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days=1)).strftime(
  508. "%Y-%m-%d"
  509. )
  510. account_with_read_rate_avg = await self.get_accounts_read_avg(dt)
  511. accounts = await self.get_publishing_accounts()
  512. forbidden_gh_ids = await self._get_forbidden_gh_ids()
  513. for account in tqdm(accounts, desc="计算单个账号的阅读均值"):
  514. if account["gh_id"] in forbidden_gh_ids:
  515. continue
  516. try:
  517. await self.cal_read_avg_detail(
  518. account=account,
  519. dt=dt,
  520. account_with_read_rate_avg=account_with_read_rate_avg,
  521. )
  522. except Exception as e:
  523. print(f"计算账号 {account['account_name']} 阅读均值失败 : {e}")
  524. print(traceback.format_exc())
  525. class AccountPositionOpenRateAvg(AccountPositionReadRateAvg):
  526. async def get_account_open_rate(self, gh_id: str, date_string: str) -> float:
  527. fetch_query = f"""
  528. select
  529. sum(view_count) as 'total_read',
  530. sum(first_level) as 'total_first_level',
  531. sum(first_level) / sum(view_count) as 'avg_open_rate'
  532. from datastat_sort_strategy
  533. where gh_id = '{gh_id}' and date_str between date_sub(str_to_date('{date_string}', '%Y%m%d'), interval {self.STAT_PERIOD} day)
  534. and str_to_date('{date_string}', '%Y%m%d');
  535. """
  536. res = await self.pool.async_fetch(query=fetch_query)
  537. return float(res[0]["avg_open_rate"]) if res else 0.0
  538. async def set_avg_open_rate_for_each_account(
  539. self, gh_id: str, date_string: str, avg_read_rate: float
  540. ) -> int:
  541. update_query = """
  542. update account_avg_info_v3
  543. set open_rate_avg = %s
  544. where gh_id = %s and update_time = %s;
  545. """
  546. return await self.pool.async_save(
  547. query=update_query,
  548. db_name="piaoquan_crawler",
  549. params=(avg_read_rate, gh_id, date_string),
  550. )
  551. async def deal(self, date_string: str | None):
  552. if not date_string:
  553. date_string = datetime.now().strftime("%Y-%m-%d")
  554. dt = (datetime.strptime(date_string, "%Y-%m-%d") - timedelta(days=1)).strftime(
  555. "%Y-%m-%d"
  556. )
  557. account_list = await self.get_publishing_accounts()
  558. forbidden_gh_ids = await self._get_forbidden_gh_ids()
  559. for account in tqdm(account_list, desc="计算单个账号的打开率均值"):
  560. if account["gh_id"] in forbidden_gh_ids:
  561. continue
  562. try:
  563. avg_open_rate = await self.get_account_open_rate(
  564. gh_id=account["gh_id"], date_string=dt.replace("-", "")
  565. )
  566. await self.set_avg_open_rate_for_each_account(
  567. gh_id=account["gh_id"],
  568. date_string=dt,
  569. avg_read_rate=avg_open_rate,
  570. )
  571. except Exception as e:
  572. print(f"计算账号 {account['account_name']} 打开率均值失败 : {e}")
  573. print(traceback.format_exc())
  574. continue