← 返回列表 hotspots.php 12.4 KB · 2026-11-06 10:47
<?php
// ===== 源码查看保护 =====
// 通过 Web 直接访问时,仅以纯文本输出自身源码,不执行任何业务逻辑。
if (PHP_SAPI !== 'cli' && basename($_SERVER['SCRIPT_FILENAME'] ?? '') === basename(__FILE__)) {
    header('Content-Type: text/plain; charset=utf-8');
    echo file_get_contents(__FILE__);
    exit;
}

/**
 * hotspots.php — 热点抓取模块
 * 为三站选题提供「当前领域热点」,避免写成无时效性的空泛文章。
 *
 * 可用源(服务器在国内,实测 200 的):
 *   通用  百度热搜 top.baidu.com
 *   科技  IT之家 / 36kr / cnBeta
 *   影评  百度娱乐榜 / 豆瓣电影 Top250
 *   摄影  色影无忌
 */

/** 带超时的 GET(gzip 解压) */
function http_get($url, $timeout = 10) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => $timeout,
        CURLOPT_CONNECTTIMEOUT => 6,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 3,
        CURLOPT_ENCODING       => '',
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => 0,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120',
    ]);
    $body = curl_exec($ch);
    curl_close($ch);
    return is_string($body) ? $body : '';
}

/** 从百度热搜/娱乐榜页面提取热搜词 */
function fetch_baidu_board($tab = 'realtime', $limit = 30) {
    $html = http_get('https://top.baidu.com/board?tab=' . urlencode($tab));
    if ($html === '') return [];
    $words = [];
    // 页面内嵌 JSON: "word":"xxx"
    if (preg_match_all('/"word"\s*:\s*"([^"]{2,40})"/u', $html, $m)) {
        foreach ($m[1] as $w) {
            $w = trim(html_entity_decode($w, ENT_QUOTES, 'UTF-8'));
            if ($w !== '' && !preg_match('/^(百度热搜|抖音|知乎|微博|好看视频|新浪|腾讯|搜狐)$/u', $w)) {
                $words[] = $w;
            }
        }
    }
    return array_slice(array_values(array_unique($words)), 0, $limit);
}

/** 从 RSS 提取标题 */
function fetch_rss_titles($url, $limit = 25) {
    $xml = http_get($url);
    if ($xml === '') return [];
    $out = [];
    if (preg_match_all('#<item[^>]*>(.*?)</item>#is', $xml, $items)) {
        foreach ($items[1] as $item) {
            if (preg_match('#<title>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</title>#is', $item, $t)) {
                $title = trim(html_entity_decode(strip_tags($t[1]), ENT_QUOTES, 'UTF-8'));
                if ($title !== '' && mb_strlen($title) <= 120) {
                    $out[] = $title;
                }
            }
        }
    } elseif (preg_match_all('#<entry[^>]*>(.*?)</entry>#is', $xml, $entries)) {
        foreach ($entries[1] as $item) {
            if (preg_match('#<title[^>]*>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</title>#is', $item, $t)) {
                $title = trim(html_entity_decode(strip_tags($t[1]), ENT_QUOTES, 'UTF-8'));
                if ($title !== '' && mb_strlen($title) <= 120) {
                    $out[] = $title;
                }
            }
        }
    }
    return array_slice(array_values(array_unique($out)), 0, $limit);
}

/**
 * 医疗 / 医药相关词(用于把这类热点从选题素材里剔掉)
 *
 * ⚠️ 刻意不收录「病毒」「感染」「移植」「试验」这类词:
 *    计算机领域天天用(电脑病毒、病毒感染、数据移植、飞行试验),
 *    单独过滤会把技术类选题一起误删。
 */
function med_block_words() {
    static $w = null;
    if ($w !== null) return $w;
    $w = [
        // 机构与人员
        '医院', '医生', '医师', '大夫', '护士', '患者', '病人', '病房', '门诊', '急诊', 'ICU',
        // 诊疗行为
        // ⚠️ 不收录「诊断」「筛查」「体检」「瘦身」:技术文章常用
        //    (故障诊断、漏洞筛查、代码体检、包体积瘦身),会误伤技术选题。
        //    医疗语境的"诊断/筛查"通常同时含「医院/医生/癌症/临床」等词,能被其他词拦住。
        '手术', '治疗', '疗法', '化疗', '放疗', '透析', '输液', '麻醉', '抢救', '临床试验',
        '确诊', '疾病', '病症', '疗效', '副作用', '不良反应', '适应症', '适应证',
        '病理', '医学影像',   // 只用于医疗语境,不会误伤技术词
        // 药品与产业
        // ★「药方」类词是踩过的坑:曾发过《药方刻在墓碑上,是传承还是失传》,
        //   当时词表只有「处方」没有「药方」,漏网了。
        '药物', '药品', '用药', '处方', '疫苗', '药企', '药价', '集采', '仿制药', '原研药',
        '药方', '中药', '草药', '汤药', '膏药', '偏方', '药丸', '药酒', '药剂',
        '药铺', '药房', '药店',
        '中药材', '中医', '西医', '针灸', '医保', '医疗', '医药',
        // 常见疾病
        '癌症', '肿瘤', '糖尿病', '高血压', '心梗', '中风', '白血病', '艾滋', '乙肝',
        '流感', '新冠', '肺炎', '抑郁症', '焦虑症', '孤独症', '自闭症',
        '阿尔茨海默', '帕金森', '痴呆',
        // ★ 器官 + 病(2026-11-06 补:曾漏掉「肝病」)
        // ⚠️ 不收「脑病」——「电脑病毒」含"脑病",会误伤所有电脑/病毒类文章
        '肝病', '肾病', '胃病', '肺病', '心脏病', '眼病', '皮肤病',
        '精神病', '慢性病', '传染病',
        '肝癌', '肺癌', '胃癌', '肾衰', '肝硬化', '脂肪肝',
        // ★ 检查项目(曾漏掉「胃镜」)
        '胃镜', '肠镜', '喉镜', 'B超', '彩超', '核磁', 'CT检查', '验血',
        // ★ 诊疗场景
        '住院', '出院', '手术台', '服药', '吃药', '药片', '注射液', '病床', '就诊', '挂号',
        // 健康养生(同样避开技术常用词:瘦身、体检)
        '养生', '保健', '保健品', '补品', '减肥药', '营养品',
    ];
    return $w;
}

/**
 * 检查一段文本是否命中医疗词
 * @return string 命中的词;未命中返回空串
 */
function med_hit($text) {
    $text = (string)$text;
    if ($text === '') return '';
    foreach (med_block_words() as $w) {
        if (mb_strpos($text, $w, 0, 'UTF-8') !== false) return $w;
    }
    return '';
}

/**
 * 过滤医疗医药类条目
 * @return array [保留的条目, 剔除数量]
 */
function filter_med_items($items) {
    $block = med_block_words();
    $keep = []; $drop = 0;
    foreach ($items as $t) {
        $hit = false;
        foreach ($block as $k) {
            if (mb_strpos((string)$t, $k, 0, 'UTF-8') !== false) { $hit = true; break; }
        }
        if ($hit) { $drop++; continue; }
        $keep[] = $t;
    }
    return [$keep, $drop];
}

/**
 * 按站点抓热点
 * 返回:数组(每项是一个候选热点)
 */
function fetch_hotspots($siteKey) {
    $hot = [];

    try {
        switch ($siteKey) {

            case 'main':   // 综合类:通用热搜 + 科技资讯
                $top = fetch_baidu_board('realtime', 25);
                if ($top) {
                    $hot[] = ['src' => '今日全网热搜', 'items' => $top];
                }
                $tech = [];
                foreach (['https://www.ithome.com/rss/', 'https://36kr.com/feed'] as $feed) {
                    $tech = array_merge($tech, fetch_rss_titles($feed, 15));
                }
                if ($tech) {
                    $hot[] = ['src' => '科技资讯(IT之家/36氪)', 'items' => array_slice(array_values(array_unique($tech)), 0, 20)];
                }
                break;

            case 'film':   // 影评:在映片 + 娱乐热搜 + 经典片库
                $ent = fetch_baidu_board('movie', 20);
                if ($ent) {
                    $hot[] = ['src' => '当前影视热搜', 'items' => $ent];
                }
                // 豆瓣口碑片库,提供「有话可说」的经典片
                $html = http_get('https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=time&page_limit=30&page_start=0');
                if ($html !== '') {
                    $names = [];
                    if (preg_match_all('#title="([^"]{1,30})"#u', $html, $m)) {
                        $names = $m[1];
                    }
                    if ($names) {
                        $hot[] = ['src' => '近期热门影片', 'items' => array_slice(array_values(array_unique($names)), 0, 15)];
                    }
                }
                break;

            case 'visual': // 摄影:IT之家影像相关 + 少数派 + 热搜筛选
                // 实测:色影无忌 RSS 已 307 跳转失效、photoworld 404,
                // 因此改用「科技媒体 + 热搜筛选」组合,稳定性更好。
                $imgKw = ['相机', '镜头', '摄影', '影像', '拍', '照片', '摄像', '画质', '像素',
                          '变焦', '焦段', '传感器', '曝光', '夜景', '长焦', '广角', '微单',
                          '单反', '无人机', '航拍', 'AI 绘画', '生成', '眼镜', '视觉', '显示',
                          '眼镜', '芯片', '内存', '硬盘', '云', '网'];
                $pool = [];
                foreach (['https://www.ithome.com/rss/', 'https://sspai.com/feed'] as $feed) {
                    $pool = array_merge($pool, fetch_rss_titles($feed, 60));
                }
                $rel = [];
                foreach ($pool as $t) {
                    foreach ($imgKw as $k) {
                        if (mb_strpos($t, $k, 0, 'UTF-8') !== false) { $rel[] = $t; break; }
                    }
                }
                if ($rel) {
                    $hot[] = ['src' => '数码影像与视觉技术动态', 'items' => array_slice(array_values(array_unique($rel)), 0, 15)];
                }

                // 通用热搜中筛出与影像/创作相关的
                $top = fetch_baidu_board('realtime', 50);
                $rel2 = [];
                foreach ($top as $w) {
                    foreach ($imgKw as $k) {
                        if (mb_strpos($w, $k, 0, 'UTF-8') !== false) { $rel2[] = $w; break; }
                    }
                }
                if ($rel2) {
                    $hot[] = ['src' => '今日热议中的影像相关话题', 'items' => array_slice(array_values(array_unique($rel2)), 0, 12)];
                }
                break;
        }
    } catch (Exception $e) {
        // 抓取失败不阻断发文
    }

    // 剔除医疗医药类选题(用户要求:自动撰写的文章不涉医疗/医药)
    $totalDrop = 0;
    foreach ($hot as &$blk) {
        list($keep, $drop) = filter_med_items($blk['items']);
        if ($drop > 0) {
            $blk['items'] = $keep;
            $blk['dropped'] = $drop;
            $totalDrop += $drop;
        }
    }
    unset($blk);
    // 过滤后变空的板块直接丢掉
    $hot = array_values(array_filter($hot, function ($b) {
        return !empty($b['items']);
    }));
    if ($totalDrop > 0) {
        $GLOBALS['gk_med_dropped'] = $totalDrop;
        echo "  (已剔除 {$totalDrop} 条医疗医药类热点)\n";
    }

    return $hot;
}

/** 渲染成可注入提示词的文本块 */
function render_hotspots($hot) {
    if (!$hot) return '';
    $out = "\n\n════════════════════════════════════\n"
         . "【今日热点素材({$hot[0]['src']} 等,实时抓取)】\n"
         . "请从以下真实发生的热点中**选定一个**作为文章切入点。\n";
    foreach ($hot as $block) {
        $out .= "\n◆ {$block['src']}\n";
        foreach (array_slice($block['items'], 0, 25) as $i => $item) {
            $out .= '  ' . ($i + 1) . '. ' . $item . "\n";
        }
    }
    $out .= "════════════════════════════════════\n";
    return $out;
}

/**
 * 命令行自测:php hotspots.php --test main
 * ⚠️ 必须靠 BASENAME 判断:本文件会被 gk_auto.php require_once,
 *    仅靠 $argv[1]==='--test' 在被载入时也可能误触发并 exit()。
 */
if (PHP_SAPI === 'cli' && basename($_SERVER['SCRIPT_FILENAME'] ?? '') === 'hotspots.php'
    && isset($argv[1]) && $argv[1] === '--test') {
    $key = $argv[2] ?? 'main';
    $h = fetch_hotspots($key);
    if (!$h) {
        echo "抓取失败(将回退到无热点模式)\n";
    } else {
        echo render_hotspots($h);
    }
    exit(0);
}