<?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);
}