{"id":546,"date":"2025-08-01T14:19:49","date_gmt":"2025-08-01T06:19:49","guid":{"rendered":"https:\/\/www.subkme.com\/?p=546"},"modified":"2025-08-06T15:59:58","modified_gmt":"2025-08-06T07:59:58","slug":"25%e5%b9%b4%e6%94%bf%e4%bc%81%e5%b8%82%e5%9c%ba%e6%8f%90%e6%95%88%e5%a2%9e%e4%ba%a7%e9%80%9a%e6%8a%a5%e8%84%9a%e6%9c%ac","status":"publish","type":"post","link":"https:\/\/subk.me\/?p=546","title":{"rendered":"25\u5e74\u653f\u4f01\u5e02\u573a\u63d0\u6548\u589e\u4ea7\u901a\u62a5\u811a\u672c"},"content":{"rendered":"<p>\u5904\u740625\u5e74\u653f\u4f01\u5e02\u573a\u63d0\u6548\u589e\u4ea7\u901a\u62a5\u811a\u672c\u3002<\/p>\n<pre class=\"wp-block-code\"><code># Author: subk\n# Time: 2025\/7\/24 17:07\n# Desc: \u5904\u740625\u5e74\u96c6\u56e2\u5e02\u573a\u63d0\u6548\u589e\u4ea7\u901a\u62a5\u6570\u636e\n# Version: 1.0.1 (Updated summary card item alignment)\nimport requests\nimport pandas as pd\nimport re\nimport smtplib\nimport schedule\nimport time\nfrom datetime import datetime, timedelta\nfrom bs4 import BeautifulSoup\nfrom email.mime.text import MIMEText\nfrom email.mime.multipart import MIMEMultipart\n\n# ========== \u7528\u6237\u914d\u7f6e ==========\nusername = &quot;su@e.com&quot;\npassword = &quot;74A33600&quot;\nsmtp_server = &quot;smtp.xx.om&quot;\nsmtp_port = 465\n\nrecipients  = [\n    &quot;suXXXXile.com&quot;\n]\n\nREGIONS = [&quot;XX&quot;]\nKEY_WEAK = [&quot;\u6218\u5ba2\u5ba2\u6237\u51c0\u589e&quot;, ]\nlast_sent_date = None\n\n# ========== \u5de5\u5177\u51fd\u6570 ==========\ndef get_dates():\n    today = datetime.now().strftime(&quot;%Y\u5e74%m\u6708%d\u65e5&quot;)\n    yesterday = (datetime.now() - timedelta(days=1)).strftime(&quot;%Y%m%d&quot;)\n    return today, yesterday\n\n# def get_dates():\n#     today = &quot;2025\u5e7407\u670824\u65e5&quot;\n#     yesterday = &quot;20250723&quot;\n#     return today,yesterday\n\ndef fetch_web_content(url: str) -&gt; str:\n    r = requests.get(url, timeout=10)\n    r.raise_for_status()\n    return r.text\n\ndef send_email(subject: str, html_body: str):\n    msg = MIMEMultipart()\n    msg[&quot;From&quot;] = username\n    msg[&quot;To&quot;] = &quot;, &quot;.join(recipients)\n    msg[&quot;Subject&quot;] = subject\n    msg.attach(MIMEText(html_body, &quot;html&quot;, &quot;utf-8&quot;))\n    with smtplib.SMTP_SSL(smtp_server, smtp_port) as s:\n        s.login(username, password)\n        s.sendmail(username, recipients, msg.as_string())\n\ndef highlight_keyword(content: str, keyword: str) -&gt; str:\n    return content.replace(keyword, f&quot;&lt;span style=&#039;background-color:#FFF59D&#039;&gt;{keyword}&lt;\/span&gt;&quot;)\n\ndef generate_styled_html_report(title: str, summary: str, main_table_html: str, back_table_html: str, top_table_html: str, match_table_html: str, original_content: str) -&gt; str:\n    &quot;&quot;&quot;\u751f\u6210\u7f8e\u5316\u540e\u7684HTML\u62a5\u544a&quot;&quot;&quot;\n    # \u83b7\u53d6\u5f53\u524d\u65f6\u95f4\uff0c\u786e\u4fdd\u5934\u90e8\u548c\u5e95\u90e8\u65f6\u95f4\u4e00\u81f4\n    current_time = datetime.now().strftime(&quot;%Y-%m-%d %H:%M&quot;)\n\n    html_template = f&quot;&quot;&quot;\n&lt;!DOCTYPE html&gt;\n&lt;html lang=&quot;zh-CN&quot;&gt;\n&lt;head&gt;\n    &lt;meta charset=&quot;UTF-8&quot;&gt;\n    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;\n    &lt;title&gt;\u653f\u4f01\u4e09\u5b63\u5ea6\u63d0\u4ea7\u589e\u6548\u901a\u62a5&lt;\/title&gt;\n    &lt;style&gt;\n        * {{\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }}\n        body {{\n            font-family: &#039;Microsoft YaHei&#039;, Arial, sans-serif;\n            background-color: #f5f5f5;\n            min-height: 100vh;\n            padding: 20px;\n            color: #333;\n        }}\n        .container {{\n            max-width: 1200px;\n            margin: 0 auto;\n            background: white;\n            border-radius: 8px;\n            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);\n            overflow: hidden;\n        }}\n        .header {{\n            background: #3498d8;\n            color: white;\n            padding: 25px;\n            text-align: center;\n        }}\n        .header h1 {{\n            font-size: 1.8em;\n            margin-bottom: 10px;\n        }}\n        .header p {{\n            font-size: 0.95em;\n            opacity: 0.9;\n        }}\n        .content {{\n            padding: 25px;\n        }}\n        .section {{\n            margin-bottom: 30px;\n        }}\n        .section-header {{\n            background: #3498db;\n            color: white;\n            padding: 12px 15px;\n            font-size: 1.1em;\n            font-weight: bold;\n            border-radius: 4px 4px 0 0;\n        }}\n        .section-content {{\n            padding: 20px;\n            background: white;\n            border: 1px solid #e0e0e0;\n            border-top: none;\n            border-radius: 0 0 4px 4px;\n        }}\n        .summary-card {{\n            background: #e8f4fc;\n            border: 1px solid #bedcf0;\n            border-radius: 6px;\n            padding: 20px;\n            margin-bottom: 25px;\n        }}\n        .summary-card h2 {{\n            font-size: 1.3em;\n            margin-bottom: 15px;\n            color: #2c3e50;\n            \/* \u6807\u9898\u4fdd\u6301\u9ed8\u8ba4\/\u5de6\u5bf9\u9f50\uff0c\u4e0d\u5c45\u4e2d *\/\n        }}\n        .summary-stats {{\n            display: flex;\n            justify-content: space-around;\n            flex-wrap: wrap;\n            gap: 15px;\n        }}\n        \/* \u5173\u952e\u4fee\u6539\uff1a\u786e\u4fdd stat-item \u5185\u7684\u6240\u6709\u5185\u5bb9\uff08\u6570\u503c\u3001\u6807\u7b7e\u3001\u6392\u540d\uff09\u90fd\u5c45\u4e2d *\/\n        .stat-item {{\n            display: flex;\n            flex-direction: column;\n            align-items: center; \/* \u5b50\u5143\u7d20\u6c34\u5e73\u5c45\u4e2d *\/\n            justify-content: center; \/* \u5b50\u5143\u7d20\u5782\u76f4\u5c45\u4e2d *\/\n            text-align: center; \/* \u6587\u672c\u5c45\u4e2d\uff08\u4f5c\u4e3a\u540e\u5907\uff09 *\/\n            min-width: 120px;\n        }}\n        .stat-number {{\n            font-size: 2em;\n            font-weight: bold;\n            color: #e74c3c;\n            \/* display: block; \u79fb\u9664\uff0c\u56e0\u4e3a flex \u5df2\u5904\u7406\u5e03\u5c40 *\/\n            margin-bottom: 4px; \/* \u4e0e\u6807\u7b7e\u4fdd\u6301\u95f4\u8ddd *\/\n        }}\n        .stat-label {{\n            font-size: 0.9em;\n            color: #555;\n            margin-bottom: 2px; \/* \u4e0e\u6392\u540d\u4fe1\u606f\u4fdd\u6301\u95f4\u8ddd *\/\n        }}\n        .rank-info {{\n            font-size: 0.75em;\n            color: #777;\n            \/* margin-top: 4px; \u79fb\u9664\uff0c\u56e0\u4e3a margin-bottom \u5df2\u5904\u7406\u95f4\u8ddd *\/\n        }}\n        \/* \u786e\u4fdd\u8868\u683c\u5185\u5bb9\u5c45\u4e2d *\/\n        table {{\n            width: 100%;\n            border-collapse: collapse;\n            margin: 15px 0;\n        }}\n        th {{\n            background: #f8f9fa;\n            color: #2c3e50;\n            padding: 10px;\n            text-align: center;\n            font-weight: bold;\n            font-size: 0.95em;\n            border: 1px solid #ddd;\n        }}\n        td {{\n            padding: 8px 10px;\n            text-align: center;\n            border: 1px solid #eee;\n            font-size: 0.9em;\n            word-wrap: break-word;\n        }}\n        tr:nth-child(even) {{\n            background-color: #fafafa;\n        }}\n        tr:hover td {{\n            background-color: #f0f7ff;\n        }}\n        tr:last-child td {{\n            background-color: #f0f0f0;\n            font-weight: bold;\n        }}\n        .table-container {{\n            overflow-x: auto;\n            margin: 15px 0;\n        }}\n        .original-content {{\n            background: #f9f9f9;\n            border: 1px solid #e0e0e0;\n            border-radius: 6px;\n            padding: 15px;\n            margin-top: 20px;\n            font-size: 0.9em;\n        }}\n        .original-content h3 {{\n            color: #2c3e50;\n            margin-bottom: 10px;\n            font-size: 1.1em;\n        }}\n        .footer {{\n            text-align: center;\n            padding: 15px;\n            background: #f8f9fa;\n            color: #666;\n            font-size: 0.85em;\n            border-top: 1px solid #eee;\n        }}\n        @media (max-width: 768px) {{\n            .summary-stats {{\n                flex-direction: column;\n                gap: 15px;\n            }}\n            .stat-item {{\n                width: 100%;\n            }}\n            .header h1 {{\n                font-size: 1.5em;\n            }}\n            .section-header {{\n                font-size: 1.05em;\n            }}\n        }}\n    &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;div class=&quot;container&quot;&gt;\n        &lt;div class=&quot;header&quot;&gt;\n            &lt;h1&gt;{title}&lt;\/h1&gt;\n            &lt;p&gt;\u6570\u636e\u66f4\u65b0\u65f6\u95f4\uff1a{current_time}&lt;\/p&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;content&quot;&gt;\n            &lt;!-- \u63cf\u8ff0\u6bb5\u843d --&gt;\n            &lt;div class=&quot;section&quot;&gt;\n                &lt;div class=&quot;section-header&quot;&gt;\n                    \ud83d\udcca \u901a\u62a5\u6982\u89c8\n                &lt;\/div&gt;\n                &lt;div class=&quot;section-content&quot;&gt;\n                    &lt;p&gt;\n                        \u6839\u636e\u653f\u4f01\u5e02\u573a\u63d0\u4ea7\u589e\u6548\u901a\u62a5\uff0c\u73b0\u5c06\u4e09\u5b63\u5ea6XX\u5206\u516c\u53f8\u60c5\u51b5\u8fdb\u884c\u901a\u62a5\u3002\u8bf7\u653f\u4f01\u90e8\u5404\u7ba1\u7406\u5458\u9ad8\u5ea6\u91cd\u89c6\uff0c\u9488\u5bf9\u5f31\u9879\u5236\u5b9a\u6539\u8fdb\u63aa\u65bd\uff0c\u786e\u4fdd\u5b8c\u6210\u5168\u5e74\u76ee\u6807\u4efb\u52a1\u3002\n                    &lt;\/p&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;!-- XX\u6570\u636e\u6458\u8981 --&gt;\n            &lt;div class=&quot;summary-card&quot;&gt;\n                &lt;h2&gt;\ud83c\udfaf XX\u5730\u533a\u6570\u636e\u6458\u8981&lt;\/h2&gt;\n                &lt;div class=&quot;summary-stats&quot;&gt;\n    &quot;&quot;&quot;\n    # \u4ece summary \u5b57\u7b26\u4e32\u4e2d\u63d0\u53d6\u4fe1\u606f\u4ee5\u786e\u4fdd\u51c6\u786e\u6027\n    import re\n    front_match = re.search(r&#039;\u524d\u4e09\u9879\u76ee\u6570(\\d+)\uff0c\u6392\u540d(\\d+)&#039;, summary)\n    back_match = re.search(r&#039;\u540e\u4e09\u9879\u76ee\u6570(\\d+)\uff0c\u6392\u540d(\\d+)&#039;, summary)\n    score_match = re.search(r&#039;\u6a21\u62df\u5f97\u5206([\\d.]+)\uff0c\u6392\u540d(\\d+)&#039;, summary)\n\n    front_count, front_rank = front_match.groups() if front_match else (&quot;0&quot;, &quot;0&quot;)\n    back_count, back_rank = back_match.groups() if back_match else (&quot;0&quot;, &quot;0&quot;)\n    score_value, score_rank = score_match.groups() if score_match else (&quot;0.00&quot;, &quot;0&quot;)\n    score_value = f&quot;{float(score_value):.2f}&quot;\n\n    html_template += f&quot;&quot;&quot;\n                    &lt;!-- \u524d\u4e09\u9879\u76ee\u6570\u53ca\u6392\u540d --&gt;\n                    &lt;div class=&quot;stat-item&quot;&gt;\n                        &lt;span class=&quot;stat-number&quot;&gt;{front_count}&lt;\/span&gt;\n                        &lt;span class=&quot;stat-label&quot;&gt;\u524d\u4e09\u9879\u76ee\u6570&lt;\/span&gt;\n                        &lt;div class=&quot;rank-info&quot;&gt;\u6392\u540d: {front_rank}&lt;\/div&gt;\n                    &lt;\/div&gt;\n\n                    &lt;!-- \u540e\u4e09\u9879\u76ee\u6570\u53ca\u6392\u540d --&gt;\n                    &lt;div class=&quot;stat-item&quot;&gt;\n                        &lt;span class=&quot;stat-number&quot;&gt;{back_count}&lt;\/span&gt;\n                        &lt;span class=&quot;stat-label&quot;&gt;\u540e\u4e09\u9879\u76ee\u6570&lt;\/span&gt;\n                        &lt;div class=&quot;rank-info&quot;&gt;\u6392\u540d: {back_rank}&lt;\/div&gt;\n                    &lt;\/div&gt;\n\n                    &lt;!-- \u6a21\u62df\u5f97\u5206\u53ca\u6392\u540d --&gt;\n                    &lt;div class=&quot;stat-item&quot;&gt;\n                        &lt;span class=&quot;stat-number&quot;&gt;{score_value}&lt;\/span&gt;\n                        &lt;span class=&quot;stat-label&quot;&gt;\u6a21\u62df\u5f97\u5206&lt;\/span&gt;\n                        &lt;div class=&quot;rank-info&quot;&gt;\u6392\u540d: {score_rank}&lt;\/div&gt;\n                    &lt;\/div&gt;\n    &quot;&quot;&quot;\n    html_template += &quot;&quot;&quot;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;!-- \u4e3b\u6570\u636e\u8868\u683c --&gt;\n            &lt;div class=&quot;section&quot;&gt;\n                &lt;div class=&quot;section-header&quot;&gt;\n                    \ud83d\udcc8 \u5404\u533a\u53bf\u5f31\u9879\u6570\u3001\u5f97\u5206\u6392\u540d\u60c5\u51b5\n                &lt;\/div&gt;\n                &lt;div class=&quot;section-content&quot;&gt;\n                    &lt;div class=&quot;table-container&quot;&gt;\n    &quot;&quot;&quot;\n    html_template += main_table_html\n    html_template += &quot;&quot;&quot;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;!-- \u540e\u4e09\u9879\u76ee\u5217\u8868 --&gt;\n            &lt;div class=&quot;section&quot;&gt;\n                &lt;div class=&quot;section-header&quot;&gt;\n                    \u26a0\ufe0f \u540e\u4e09\u9879\u76ee\u5217\u8868\n                &lt;\/div&gt;\n                &lt;div class=&quot;section-content&quot;&gt;\n                    &lt;div class=&quot;table-container&quot;&gt;\n    &quot;&quot;&quot;\n    html_template += back_table_html\n    html_template += &quot;&quot;&quot;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;!-- \u524d\u4e09\u9879\u76ee\u5217\u8868 --&gt;\n            &lt;div class=&quot;section&quot;&gt;\n                &lt;div class=&quot;section-header&quot;&gt;\n                    \ud83c\udfc6 \u524d\u4e09\u9879\u76ee\u5217\u8868\n                &lt;\/div&gt;\n                &lt;div class=&quot;section-content&quot;&gt;\n                    &lt;div class=&quot;table-container&quot;&gt;\n    &quot;&quot;&quot;\n    html_template += top_table_html\n    html_template += &quot;&quot;&quot;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;!-- \u91cd\u70b9\u5f31\u9879\u5339\u914d --&gt;\n            &lt;div class=&quot;section&quot;&gt;\n                &lt;div class=&quot;section-header&quot;&gt;\n                    \ud83d\udd0d \u5468\u8c03\u5ea6\u91cd\u70b9\u5f31\u9879\u5339\u914d\n                &lt;\/div&gt;\n                &lt;div class=&quot;section-content&quot;&gt;\n                    &lt;div class=&quot;table-container&quot;&gt;\n    &quot;&quot;&quot;\n    html_template += match_table_html\n    html_template += &quot;&quot;&quot;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n            &lt;!-- \u539f\u59cb\u9875\u9762\u5185\u5bb9 --&gt;\n            &lt;div class=&quot;section&quot;&gt;\n                &lt;div class=&quot;section-header&quot;&gt;\n                    \ud83d\udccb \u539f\u59cb\u9875\u9762\u5185\u5bb9\uff08\u542bXX\u9ad8\u4eae\uff09\n                &lt;\/div&gt;\n                &lt;div class=&quot;section-content&quot;&gt;\n                    &lt;div class=&quot;original-content&quot;&gt;\n                        &lt;h3&gt;\u9875\u9762\u539f\u59cb\u6570\u636e&lt;\/h3&gt;\n    &quot;&quot;&quot;\n    html_template += f&quot;&lt;p&gt;{original_content}&lt;\/p&gt;&quot;\n    html_template += &quot;&quot;&quot;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n        &lt;div class=&quot;footer&quot;&gt;\n            &lt;p&gt;&copy; 2025 \u4e2d\u56fd\u79fb\u52a8\u901a\u4fe1\u96c6\u56e2\u6c5f\u82cf\u6709\u9650\u516c\u53f8XX\u5206\u516c\u53f8 | \u6570\u636e\u66f4\u65b0\u65f6\u95f4\uff1a{current_time}&lt;\/p&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n    &quot;&quot;&quot;\n    return html_template\n\ndef print_styled_table(df: pd.DataFrame, title: str) -&gt; str:\n    last = df.index[-1]\n    styler = (\n        df.style\n          .set_table_attributes(&#039;style=&quot;margin-left:auto;margin-right:auto;border-collapse:collapse;&quot;&#039;)\n          .set_caption(f&quot;&lt;strong style=&#039;font-size:1.3em;color:#2E86C1&#039;&gt;{title}&lt;\/strong&gt;&quot;)\n          .set_table_styles([\n              {&quot;selector&quot;:&quot;thead th&quot;,\n               &quot;props&quot;:[(&quot;background-color&quot;,&quot;#F8F9FA&quot;),(&quot;font-weight&quot;,&quot;bold&quot;),\n                        (&quot;font-size&quot;,&quot;0.95em&quot;),(&quot;border&quot;,&quot;1px solid #ddd&quot;),(&quot;padding&quot;,&quot;10px&quot;)]},\n              {&quot;selector&quot;:&quot;tbody th&quot;, &quot;props&quot;:[(&quot;border&quot;,&quot;1px solid #eee&quot;),(&quot;padding&quot;,&quot;8px 10px&quot;)]}\n          ])\n          .set_properties(**{&quot;border&quot;:&quot;1px solid #eee&quot;,&quot;padding&quot;:&quot;8px 10px&quot;,&quot;text-align&quot;:&quot;center&quot;,&quot;word-wrap&quot;:&quot;break-word&quot;})\n          .apply(lambda row: [&quot;background-color:#f0f0f0&quot; if row.name==last else &quot;&quot; for _ in row], axis=1)\n    )\n    return f&quot;&lt;div style=&#039;margin:15px 0;&#039;&gt;{styler.to_html()}&lt;\/div&gt;&quot;\n\ndef enhance_table_border(table_html: str) -&gt; str:\n    soup = BeautifulSoup(table_html, &quot;html.parser&quot;)\n    table = soup.find(&quot;table&quot;)\n    if table:\n        table[&#039;border&#039;] = &quot;0&quot;\n        style = table.get(&#039;style&#039;, &#039;&#039;)\n        style += &#039;;width:100%;border-collapse:collapse;margin:15px 0;&#039;\n        table[&#039;style&#039;] = style\n        for th in soup.find_all(&#039;th&#039;):\n            th_style = th.get(&#039;style&#039;, &#039;&#039;)\n            th_style += &#039;;background:#f8f9fa;color:#2c3e50;padding:10px;text-align:center;font-weight:bold;font-size:0.95em;border:1px solid #ddd;&#039;\n            th[&#039;style&#039;] = th_style\n        for td in soup.find_all(&#039;td&#039;):\n            td_style = td.get(&#039;style&#039;, &#039;&#039;)\n            td_style += &#039;;padding:8px 10px;text-align:center;border:1px solid #eee;font-size:0.9em;word-wrap: break-word;&#039;\n            td[&#039;style&#039;] = td_style\n        rows = soup.find_all(&#039;tr&#039;)\n        for i, row in enumerate(rows):\n            if i % 2 == 0:\n                row_style = row.get(&#039;style&#039;, &#039;&#039;)\n                row_style += &#039;;background-color:#fafafa;&#039;\n                row[&#039;style&#039;] = row_style\n    return str(soup)\n\n# ========== \u63d0\u53d6\u51fd\u6570 ==========\ndef parse_main_table(soup: BeautifulSoup) -&gt; pd.DataFrame:\n    table = soup.find(&quot;table&quot;, class_=&quot;displayTable&quot;)\n    if not table:\n        raise ValueError(&quot;\u672a\u627e\u5230\u4e3b\u8868 displayTable&quot;)\n    df = pd.read_html(str(table), header=1)[0]\n    df.columns = [c.strip().replace(&quot;\\n&quot;, &quot;&quot;) for c in df.columns]\n    df = df[df[&quot;\u53bf\u533a&quot;].notnull() &amp; (df[&quot;\u53bf\u533a&quot;] != &quot;&quot;)]\n    for col in [&quot;\u524d\u4e09\u9879\u76ee\u6570&quot;, &quot;\u540e\u4e09\u9879\u76ee\u6570&quot;, &quot;\u6a21\u62df\u5f97\u5206&quot;, &quot;\u843d\u540e\u65f6\u5e8f\u9879\u76ee\u6570&quot;, &quot;\u6392\u540d&quot;]:\n        if col in df.columns:\n            df[col] = pd.to_numeric(df[col], errors=&quot;coerce&quot;)\n            if col == &quot;\u6a21\u62df\u5f97\u5206&quot;:\n                df[col] = df[col].round(2)\n            else:\n                df[col] = df[col].fillna(0).astype(int)\n    return df\n\ndef extract_lianshui(df: pd.DataFrame) -&gt; dict:\n    sub = df[df[&quot;\u53bf\u533a&quot;]==&quot;XX&quot;]\n    if sub.empty:\n        raise ValueError(&quot;\u672a\u627e\u5230XX\u6570\u636e&quot;)\n    front = int(sub[&quot;\u524d\u4e09\u9879\u76ee\u6570&quot;].values[0])\n    back  = int(sub[&quot;\u540e\u4e09\u9879\u76ee\u6570&quot;].values[0])\n    score = float(sub[&quot;\u6a21\u62df\u5f97\u5206&quot;].values[0])\n\n    # \u8ba1\u7b97\u6392\u540d (\u964d\u5e8f)\n    front_rank_df = df.sort_values(&quot;\u524d\u4e09\u9879\u76ee\u6570&quot;, ascending=False).reset_index(drop=True)\n    front_rank = int(front_rank_df[front_rank_df[&quot;\u53bf\u533a&quot;] == &quot;XX&quot;].index[0] + 1)\n\n    # \u540e\u4e09\u9879\u76ee\u6570\u6392\u540d (\u964d\u5e8f) - \u9879\u76ee\u6570\u591a\u7684\u6392\u540d\u9ad8\n    back_rank_df = df.sort_values(&quot;\u540e\u4e09\u9879\u76ee\u6570&quot;, ascending=False).reset_index(drop=True) \n    back_rank = int(back_rank_df[back_rank_df[&quot;\u53bf\u533a&quot;] == &quot;XX&quot;].index[0] + 1)\n\n    # \u6a21\u62df\u5f97\u5206\u6392\u540d (\u964d\u5e8f)\n    score_rank_df = df.sort_values(&quot;\u6a21\u62df\u5f97\u5206&quot;, ascending=False).reset_index(drop=True)\n    score_rank = int(score_rank_df[score_rank_df[&quot;\u53bf\u533a&quot;] == &quot;XX&quot;].index[0] + 1)\n\n    return {\n        &#039;front&#039;: front, &#039;back&#039;: back, &#039;score&#039;: score,\n        &#039;front_rank&#039;: front_rank, &#039;back_rank&#039;: back_rank, &#039;score_rank&#039;: score_rank\n    }\n\ndef extract_metrics(soup: BeautifulSoup, color: str) -&gt; dict:\n    result = {r: [] for r in REGIONS}\n    for d in soup.find_all(&quot;div&quot;, class_=&quot;title_content&quot;):\n        txt = d.get_text(strip=True)\n        m = re.match(r&#039;^\\d+\u3001(.+?)\u4e09\u5b63\u5ea6&#039;, txt)\n        if not m: continue\n        metric = m.group(1).strip()\n        for f in d.find_all(&quot;font&quot;, attrs={&quot;color&quot;: color}):\n            items = re.findall(r&#039;([\\u4e00-\\u9fa5]+)\\([^)]*\\)&#039;, f.get_text())\n            for region in items:\n                if region in result:\n                    result[region].append(metric)\n    return result\n\n# ========== \u4e3b\u62a5\u8868\u751f\u6210\u51fd\u6570 ==========\ndef generate_report():\n    today, yesterday = get_dates()\n    url = f&quot;http:\/\/10.33.222.52:31002\/hamobile\/table\/mailReport?flag=jtReport5&amp;cfg_id=207&amp;day_time={yesterday}&quot;\n    html = fetch_web_content(url)\n    soup = BeautifulSoup(html, &quot;html.parser&quot;)\n    parts = []\n    desc_div = soup.find(&quot;div&quot;, class_=&quot;title_content&quot;)\n    title_html = str(desc_div) if desc_div else &quot;&lt;p&gt;\u65e0\u63cf\u8ff0\u4fe1\u606f&lt;\/p&gt;&quot;\n    parts.append(title_html)\n    main_table = soup.find(&quot;table&quot;, class_=&quot;displayTable&quot;)\n    if not main_table:\n        raise ValueError(&quot;\u4e3b\u8868\u672a\u627e\u5230&quot;)\n    df_main = parse_main_table(soup)\n    lianshui = extract_lianshui(df_main)\n    # \u66f4\u65b0\u6458\u8981\u6587\u672c\uff0c\u5305\u542b\u6a21\u62df\u5f97\u5206\u548c\u6392\u540d\n    summary = (f&quot;XX\u524d\u4e09\u9879\u76ee\u6570{lianshui[&#039;front&#039;]}\uff0c\u6392\u540d{lianshui[&#039;front_rank&#039;]}\uff1b&quot;\n               f&quot;\u540e\u4e09\u9879\u76ee\u6570{lianshui[&#039;back&#039;]}\uff0c\u6392\u540d{lianshui[&#039;back_rank&#039;]}\u3002&quot;\n               f&quot;\u6a21\u62df\u5f97\u5206{lianshui[&#039;score&#039;]:.2f}\uff0c\u6392\u540d{lianshui[&#039;score_rank&#039;]}\u3002&quot;)\n\n    main_table_html = enhance_table_border(str(main_table))\n    back = extract_metrics(soup, &quot;red&quot;)\n    maxlen = max(len(v) for v in back.values())\n    df_back = pd.DataFrame({r: back[r]+[&#039;&#039;]*(maxlen-len(back[r])) for r in REGIONS})\n    df_back.loc[df_back.shape[0]] = {r: len(back[r]) for r in REGIONS}\n    df_back.index = [f&quot;\u540e\u4e09\u9879\u76ee{i+1}&quot; for i in range(df_back.shape[0]-1)] + [&quot;\u540e\u4e09\u9879\u76ee\u4e2a\u6570&quot;]\n    back_table_html = print_styled_table(df_back, &quot;\u540e\u4e09\u9879\u76ee\u5217\u8868&quot;)\n    top = extract_metrics(soup, &quot;blue&quot;)\n    maxlen2 = max(len(v) for v in top.values())\n    df_top = pd.DataFrame({r: top[r]+[&#039;&#039;]*(maxlen2-len(top[r])) for r in REGIONS})\n    df_top.loc[df_top.shape[0]] = {r: len(top[r]) for r in REGIONS}\n    df_top.index = [f&quot;\u524d\u4e09\u9879\u76ee{i+1}&quot; for i in range(df_top.shape[0]-1)] + [&quot;\u524d\u4e09\u9879\u76ee\u4e2a\u6570&quot;]\n    top_table_html = print_styled_table(df_top, &quot;\u524d\u4e09\u9879\u76ee\u5217\u8868&quot;)\n    matched = {r: [m for m in back[r] if m in KEY_WEAK] for r in REGIONS}\n    maxlen3 = max(len(v) for v in matched.values())\n    df_match = pd.DataFrame({r: matched[r]+[&#039;&#039;]*(maxlen3-len(matched[r])) for r in REGIONS})\n    df_match.loc[df_match.shape[0]] = {r: len(matched[r]) for r in REGIONS}\n    df_match.index = [f&quot;\u91cd\u70b9\u5f31\u9879{i+1}&quot; for i in range(df_match.shape[0]-1)] + [&quot;\u91cd\u70b9\u5f31\u9879\u4e2a\u6570&quot;]\n    match_table_html = print_styled_table(df_match, &quot;\u5468\u8c03\u5ea6\u91cd\u70b9\u5f31\u9879\u5339\u914d&quot;)\n    highlighted = highlight_keyword(html, &quot;XX&quot;)\n    report_html = generate_styled_html_report(\n        f&quot;\u3010\u62a2\u5148\u7248\u3011\u653f\u4f01\u63d0\u6548\u589e\u4ea7\u901a\u62a5\uff1a{summary}&quot;,\n        summary,\n        main_table_html,\n        back_table_html,\n        top_table_html,\n        match_table_html,\n        highlighted\n    )\n    return f&quot;\u3010\u62a2\u5148\u7248\u3011\u653f\u4f01\u63d0\u6548\u589e\u4ea7\u901a\u62a5\uff1a{summary}&quot;, report_html\n\n# ========== \u8c03\u5ea6\u903b\u8f91 ==========\ndef job_wrapper():\n    global last_sent_date\n    now = datetime.now()\n    today_str = now.strftime(&quot;%Y%m%d&quot;)\n    if now.hour &gt;= 15 and last_sent_date != today_str:\n        try:\n            subject, body = generate_report()\n            send_email(subject, body)\n            last_sent_date = today_str\n            print(f&quot;[{now}] \u62a5\u544a\u5df2\u53d1\u9001&quot;)\n        except Exception as e:\n            print(f&quot;[{now}] \u751f\u6210\u5931\u8d25\uff1a{e}&quot;)\n\ndef main():\n    global last_sent_date\n    # --- \u624b\u52a8\u6267\u884c\u90e8\u5206 ---\n    try:\n        subject, body = generate_report()\n        send_email(subject, body)\n        if datetime.now().hour &gt;= 15:\n            last_sent_date = datetime.now().strftime(&quot;%Y%m%d&quot;)\n        print(&quot;\u624b\u52a8\u6267\u884c\u6210\u529f\uff0c\u5df2\u53d1\u9001\u90ae\u4ef6\u3002&quot;)\n    except Exception as e:\n        print(&quot;\u624b\u52a8\u6267\u884c\u5931\u8d25\uff1a&quot;, e)\n    # --- \u8c03\u5ea6\u90e8\u5206 ---\n    schedule.every(5).minutes.do(job_wrapper)\n    print(&quot;\u8c03\u5ea6\u4efb\u52a1\u5df2\u542f\u52a8\uff0c\u6309 Ctrl+C \u9000\u51fa...&quot;)\n    try:\n        while True:\n            schedule.run_pending()\n            time.sleep(1)\n    except KeyboardInterrupt:\n        print(&quot;\\n\u7a0b\u5e8f\u5df2\u6536\u5230\u4e2d\u65ad\u4fe1\u53f7\uff0c\u6b63\u5728\u9000\u51fa...&quot;)\n    # --- \u8c03\u5ea6\u90e8\u5206\u7ed3\u675f ---\n\nif __name__ == &quot;__main__&quot;:\n    main()<\/code><\/pre>","protected":false},"excerpt":{"rendered":"<p>\u5904\u740625\u5e74\u653f\u4f01\u5e02\u573a\u63d0\u6548\u589e\u4ea7\u901a\u62a5\u811a\u672c\u3002<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[23],"class_list":["post-546","post","type-post","status-publish","format-standard","hentry","category-it","tag-python"],"_links":{"self":[{"href":"https:\/\/subk.me\/index.php?rest_route=\/wp\/v2\/posts\/546","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/subk.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/subk.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/subk.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/subk.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=546"}],"version-history":[{"count":5,"href":"https:\/\/subk.me\/index.php?rest_route=\/wp\/v2\/posts\/546\/revisions"}],"predecessor-version":[{"id":575,"href":"https:\/\/subk.me\/index.php?rest_route=\/wp\/v2\/posts\/546\/revisions\/575"}],"wp:attachment":[{"href":"https:\/\/subk.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=546"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/subk.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=546"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/subk.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=546"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}