简介基于PHP的必应Bing网页搜索程序v1.0源码面向PHP初中级开发者演示如何通过官方搜索API搭建一套完整的网页搜索应用。资源共27个文件包含4个PHP脚本如请求处理、搜索分发、配置类、5个JS交互脚本、3个CSS样式表以及多张页面配图、图标和说明文档整体仅148KB结构紧凑适合快速阅读与本地部署。已有256人浏览学习。代码覆盖API配置、请求发送、JSON/XML解析、结果渲染等关键环节附带的说明txt与目录内图片资源可直接对应前端展示便于理解Bing搜索从发起查询到呈现结果的完整链路。对于想掌握第三方搜索接口集成、或希望仿写搜索引擎页面的开发者这份源码提供了简洁的参考范例尤其适合用来学习cURL请求、API密钥管理以及PHP与前端交互的基础写法。1. 拿到这份Bing搜索PHP源码先别急着上传服务器PHP开发者下载了《基于PHP的必应bing网页搜索php程序v1.0源码.zip》本能动作是解压、传服务器、填个关键词看能不能吐出结果。这个程序做的事情其实很窄接收一个关键词向必应发出请求把标题、URL、摘要整理成PHP数组交给页面循环输出。它不是搜索引擎更像一层搜索代理。适合两类人一类是站内缺搜索功能、想在两天内接入必应结果的PHP后端开发另一类是做外包交付客户只要求能搜出网页列表的场景。拿到源码先别急着跑把数据来源看清楚后面才不踩坑。2. 两条数据路线Bing Search API 与网页抓取先选对再写码2.1 标题里的“网页搜索”是什么v1.0源码常见的两种实现路径“网页搜索”这四个字有歧义。第一种理解是“搜索网页内容”这是产品语义第二种理解是“程序去抓网页版的必应”也就是直接请求https://www.bing.com/search这个HTML页面。市面上以“v1.0源码”命名的Bing搜索PHP程序多数是第二种不用申请Key、不用买额度打包者写一个类解析HTML就能交付。判断手上源码走的是哪条路第一件事是搜一下请求目标地址。常见做法是在项目目录里执行下面的命令看到api.bing.microsoft.com说明封装的是官方接口看到www.bing.com/search则是HTML抓取。# 解压后先定位HTTP请求目标这一步决定后面配置方向 grep -r api.microsoft\|bing.com/search --include*.php .这条命令能快速区分API和抓取两种实现。如果什么都不匹配说明源码可能把请求地址藏在配置文件或一个独立的curl封装函数里再搜curl_setopt顺着URL参数找。两条路线的配置和维护成本差别很大下面分别给出最小可运行代码但先说结论能接API就不要只做抓取。2.2 从API Key到第一条结果Bing Search v7 的 cURL 请求写法官方接口现在统一走 Bing Search v7请求头带Ocp-Apim-Subscription-Key。申请Key之后PHP里我习惯用cURL而不是file_get_contents可以单独设置连接超时与总超时还能拿到HTTP状态码排查401、403时少走弯路。下面是能直接落地的请求片段。?php // 请求Bing Search API v7的最简实现 $key 替换成你的Key; $query urlencode(PHP 数组转字符串); // 中文关键词必须编码 $url https://api.bing.microsoft.com/v7.0/search?q . $query . count10mktzh-CN; $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, [ Ocp-Apim-Subscription-Key: . $key, ]); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response curl_exec($ch); if (curl_errno($ch)) { echo cURL错误: . curl_error($ch); } curl_close($ch); $data json_decode($response, true); if (isset($data[webPages][value])) { foreach ($data[webPages][value] as $item) { echo $item[name] . PHP_EOL; echo $item[url] . PHP_EOL; echo $item[snippet] . PHP_EOL; } }逻辑说明cURL把请求发到v7接口返回的JSON里网页结果一定在webPages.value数组内每项包含name、url、snippet。这个片段的关键是CURLOPT_TIMEOUT和HTTPHEADER前者保证请求最多等10秒后者是Bing识别身份的固定请求头Key不是放在URL参数里的。参数说明q是唯一必填参数count控制返回条数最大50mktzh-CN让结果偏向中文区域注意这里只编码了q如果还要拼其他参数推荐用http_build_query直接生成查询串避免漏编码。2.3 免Key方案直接请求 www.bing.com/search 的HTML解析这类v1.0源码最常见的路径就是抓HTML优点是零费用零申请缺点后文再说。先给出最小解析代码注意请求头里的User-Agent必须补完整否则很容易触发必应的机器访问策略。?php // 抓取必应网页版并解析结果仅作为演示和临时降级方案 $url https://www.bing.com/search?q . urlencode(PHP 开源项目) . mktzh-CN; $ch curl_init(); curl_setopt_array($ch, [ CURLOPT_URL $url, CURLOPT_RETURNTRANSFER true, CURLOPT_FOLLOWLOCATION true, CURLOPT_USERAGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, CURLOPT_TIMEOUT 10, ]); $html curl_exec($ch); curl_close($ch); $doc new DOMDocument(); $doc-loadHTML($html); // 用于忽略HTML不规范产生的警告 $xpath new DOMXPath($doc); $nodes $xpath-query(//li[contains(class, b_algo)]//h2/a); $results []; foreach ($nodes as $node) { $results[] [ title $node-textContent, url $node-getAttribute(href), ]; } print_r($results);逻辑说明必应网页版的结果块以li.b_algo为容器标题在里面的h2 a。DOMXPath用contains(class, b_algo)做模糊匹配模板类名变化时还能有一定容错空间。参数说明FOLLOWLOCATION处理跳转UA尽量接近真实浏览器mktzh-CN对网页版一样生效。这段代码最大的问题在于依赖HTML结构必应调整一次前端模板解析就会空手而归。v1.0源码如果只有这套逻辑生产环境上线前至少得准备API降级否则搜索功能是标准的黑匣子跑着跑着就翻车。3. PHP端最小可运行代码把Bing结果封装成可复用的搜索类3.1 运行环境与目录PHP 7.4以上、cURL扩展、cache目录先确认运行环境。这段源码依赖三个东西PHP 7.4以上推荐8.0php-curl扩展以及cache目录的写权限。很多人拿到这类源码包直接扔到面板结果ext-curl没装报Call to undefined function curl_init()第一眼以为代码坏了其实环境缺扩展。先执行php -m | grep curl确认扩展存在再谈下一步。我一般会这样组织一个可运行版本project/ ├── BingSearchClient.php # 搜索客户端类 ├── search.php # 搜索页面入口 ├── cache/ # 结果缓存目录需要写权限 └── logs/ # 请求日志目录这类源码的二次开发重点不在页面样式而是把搜索逻辑收敛成一个PHP类。后续要接php后端框架时也只需要把BingSearchClient挂到服务容器里Controller层一行调用就够。3.2 封装BingSearchClient缓存、超时、异常一起收进类里写一个稍微完整的PHP类。比散落的函数好在哪调用方只需要new一下再传关键词不用关心请求头、Key、缓存路径这些细节。?php // BingSearchClient.php // 封装缓存、超时、异常处理的Bing搜索客户端 class BingSearchClient { private string $key; private int $timeout; private string $cacheDir; public function __construct(string $key, int $timeout 8) { $this-key $key; $this-timeout $timeout; $this-cacheDir __DIR__ . /cache; if (!is_dir($this-cacheDir)) { mkdir($this-cacheDir, 0755, true); } } public function search(string $query, array $params []): array { // 缓存Key必须包含参数避免两个请求互串结果 $cacheKey bing_ . md5($query . serialize($params)); $cacheFile $this-cacheDir . / . $cacheKey . .json; // 命中一小时内的缓存就直接返回 if (is_file($cacheFile) time() - filemtime($cacheFile) 3600) { return json_decode(file_get_contents($cacheFile), true); } $queryParams array_merge([ q $query, mkt zh-CN, count 10, ], $params); $url https://api.bing.microsoft.com/v7.0/search? . http_build_query($queryParams); $ch curl_init(); curl_setopt_array($ch, [ CURLOPT_URL $url, CURLOPT_RETURNTRANSFER true, CURLOPT_HTTPHEADER [Ocp-Apim-Subscription-Key: . $this-key], CURLOPT_TIMEOUT $this-timeout, CURLOPT_CONNECTTIMEOUT 3, ]); $body curl_exec($ch); $code curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code ! 200) { throw new RuntimeException(Bing返回HTTP . $code . . $body); } $result json_decode($body, true); file_put_contents($cacheFile, json_encode($result), LOCK_EX); return $result; } }逻辑说明search方法先按关键词和参数生成缓存Key一小时内的结果直接读文件避免每次搜索都消耗API配额。请求失败时抛异常让上层决定是降级还是提示用户。参数说明timeout默认8秒CURLOPT_CONNECTTIMEOUT单独设3秒这两个参数一个管连不上一个管连上但服务端慢都设上才不会出现一个慢请求拖死PHP-FPM的情况。http_build_query会做URL编码比手拼字符串安全得多。3.3 输出层搜索页拿到结果数组之后的循环渲染类封装好后页面层就很简单。search.php负责接收q参数、调用客户端、循环输出。注意这里要做空值拦截q为空直接不请求。?php // search.php require BingSearchClient.php; $q trim($_GET[q] ?? ); $results []; if ($q ! ) { $client new BingSearchClient(替换成你的Key); try { $data $client-search($q); $results $data[webPages][value] ?? []; } catch (Throwable $e) { error_log(Bing搜索失败: . $e-getMessage()); } } ? !DOCTYPE html html headmeta charsetutf-8title搜索/title/head body form methodget input typetext nameq value? htmlspecialchars($q, ENT_QUOTES) ? button typesubmit搜索/button /form ?php foreach ($results as $item): ? h3a href? htmlspecialchars($item[url]) ? target_blank relnoopener? htmlspecialchars($item[name]) ?/a/h3 p? htmlspecialchars($item[snippet] ?? ) ?/p ?php endforeach; ? /body /html逻辑说明这个页面把PHP数组流转平铺出来核心只有三行请求、取值、循环。htmlspecialchars做输出转义是必须的因为必应返回的snippet可能带HTML标签直接echo会把不干净的标记打进页面顺手也给XSS留了门。参数说明url和name在API里不总是同时存在渲染前用?? 兜底。这个页面本身没有缓存逻辑因为缓存已经在客户端类里做掉了。3.4 一个容易被忽略的细节文件锁与缓存并发写缓存写入要加LOCK_EX。两个请求同时写同一个缓存文件时不加锁会出现半个JSONjson_decode返回null然后整页空白。文件锁就是给写入过程上一道互斥锁先到的写、后到的等。读取时同样要注意判断文件存在后立即读中间不要插入其他逻辑。实际写缓存时还有一种翻车现象cache目录属主是rootPHP-FPM以www用户运行file_put_contents返回false代码还没走到请求Bing就报错了。所以构造函数里mkdir之后最好顺手判断is_writable不通过直接抛异常把问题拦在页面层之外而不是等用户点击搜索时才看到一个白屏。4. 四个必调参数把Bing搜索从“能通”调到“好用”很多人找必应高级搜索入口其实必应把高级搜索能力都拆成了查询参数。官方接口里同样的关键词参数不同结果数量、区域倾向、内容尺度差别很大。v1.0源码默认参数往往只给了q实际能调的至少有下面这些。4.1 mkt 与 setlang搜索结果的语言和地域权重mkt是市场代码直接影响结果的区域排序。做中文站用zh-CN做英文内容站用en-US。mkt传错最常见的表现是关键词是中文但返回的URL很多是英文页面或者前几页跟目标站点完全不搭。setlang是另一个老参数很多抓网页版的源码里能看到。官方API里setlang的优先级低于mkt所以我在客户端类里只保留mkt把setlang留给老代码兼容。组合示例$params [ q $query, mkt zh-CN, count 10, offset 0, safeSearch Moderate, textFormat HTML, ]; $url https://api.bing.microsoft.com/v7.0/search? . http_build_query($params);逻辑说明http_build_query会把中文正确编码还能处理数组参数比字符串拼接省心。参数说明如果想调整结果密度把count改成20Bing单次最多返回50条多出来的用offset翻页。4.2 count 和 offset一页10条还是20条翻页怎么处理count控制本次返回条数默认10上限50。offset是偏移量第一页填0下一页填上一页的条数。Bing API没有像数据库那样的页码概念翻页就是叠加偏移第一页offset0count10第二页offset10count10以此类推。分页时有一个取舍count越大单次请求越重解析越慢count太小翻页次数多API配额消耗快。我一般第一页10条后续翻页每次20条因为用户很少翻过第三页。缓存策略也要跟着改第一页缓存1小时第二页以后缓存15分钟就够热门词流量基本都打在第一页。offset的另一个用途是搜索框联想服务端把用户输到一半的词先请求一次取前3条结果做下拉提示这个用法对配额消耗较大接入前要评估频控。4.3 safeSearch 与 textDecorations结果质量和展示格式safeSearch有三个取值Strict、Moderate、Off。做面向大众的站点建议至少Moderate做工具型站点且有规避需求用Strict。这个参数影响的是搜索结果里是否夹带不适合公开展示的内容站点不想惹麻烦就别选Off。textDecorations控制返回的snippet是否带加粗标记配textFormatHTML时摘要里的查询词会被b包裹。拿到这种片段前端要做两步先htmlspecialchars转义再谨慎地允许指定HTML标签透出。很多新手直接echo把b当纯文本显示出来结果页面上一堆尖括号排查半天才发现是输出转义顺序没理清。4.4 超时、重试与退避cURL配置和API限流应对接口不是永远200。状态码429表示限流5xx表示必应服务端抖动。遇到这两种情况PHP侧不能死等要做退避重试。for ($attempt 1; $attempt 3; $attempt) { $response requestBing($url); // 假设这里封装了cURL请求 $status $response[http_code]; if ($status 429 || $status 500) { sleep($attempt * 2); // 第一次等2秒第二次等4秒 continue; } break; }逻辑说明退避让每次重试的等待时间递增避免集中重试把限流打得更狠。参数说明429的响应头里通常带Retry-After有就优先读这个值没有这个头再走递增等待。4xx错误不要重试重试也是同样的错。这种重试逻辑只有付费API场景才值得写抓网页版那种方式遇到中间页只能歇着。参数推荐值作用mktzh-CN / en-US决定结果的语言与区域权重count10 / 20单页返回条数上限50offset0 / 20 / 40翻页偏移safeSearchModerate过滤不适合公开展示的内容textFormatHTML / Raw摘要是否带HTML标记CURLOPT_TIMEOUT8~10整个请求的总超时CURLOPT_CONNECTTIMEOUT3连接阶段的等待上限这七个值调好接口返回质量基本就够用了。剩下的是看返回数据的结构变化也就是下一章的坑。5. Bing搜索PHP的常见坑与排查5 条血泪记录5.1 401 Access Denied但Key明明复制对了现象代码和文档一样请求却返回HTTP 401body里是一段Access Denied。原因最常见的不是Key错了而是请求头拼错。Key里如果混入不可见空格或者配置项读取时带上了引号都会401。这类v1.0源码里还有一些写死了旧endpoint把Key发到了api.cognitive.microsoft.com现在统一用https://api.bing.microsoft.com/v7.0/search。解决先用curl命令在命令行单独验证排除PHP代码干扰。KEY你的Key curl -s -H Ocp-Apim-Subscription-Key: $KEY \ https://api.bing.microsoft.com/v7.0/search?qphpcount1逻辑说明curl通了问题在PHP拼装curl也401检查Key复制和endpoint。这一步能把“代码问题”和“配置问题”快速切分开不用在源码里瞎猜。5.2 中文关键词返回0条英文却正常现象qPHP有结果qPHP 数组转字符串返回空数组页面显示“没有找到结果”。原因中文没做URL编码或者查询串里混入了未转义的字符。另一个隐蔽原因是mkt没传Bing按默认市场返回中文关键词在非中文市场的匹配率极低。解决客户端类里统一走http_build_querymkt显式写zh-CN。排查时先打印最终请求URL看中文是否被编码成%E6%95%B0%E7%BB%84这种形态而不是原样出现在地址栏。5.3 同一个请求一会儿快一会儿慢现象同一个词第一次1.2秒第二次8秒偶尔超时白屏。原因没有缓存每次查询都真正打到必应服务端cURL没有设置连接超时TCP层卡住时PHP-FPM进程被占满表现为整站变慢。解决加本地文件缓存TTL建议1800秒cURL配置CURLOPT_CONNECTTIMEOUT为3秒。上线后记录请求耗时日志$t0 microtime(true); $data $client-search($q); $cost microtime(true) - $t0; error_log(search cost{$cost}s q . $q);逻辑说明这行日志能区分慢在Bing网络还是慢在页面渲染。如果cost普遍大于5秒去查API配额和网络链路如果cost很小但页面白屏问题在PHP解析和模板输出。5.4 抓网页版时拿到的是“安全验证”页面现象解析HTML的源码返回空列表把HTML存下来一看页面上是安全验证提示一个结果块都没有。原因请求频率太高、UA不完整或缺少必要的Cookie状态。必应对脚本行为有检测短时间高频请求就会触发中间页。解决把抓取频率降到每秒1次以下UA补全成真实浏览器格式尽量保留同一个Cookie池。即便如此还是会被识别所以这条路的结局通常是换成API。我个人的判断是抓HTML只适合Demo和低峰试验v1.0源码如果只靠抓取上线前必须准备API降级否则就是给自己埋定时炸弹。5.5 json_encode 以后中文全变成 \uXXXX现象接口返回给前端的中文摘要变成\u4e2d\u6587这种样式页面显示满屏反斜杠。原因json_encode默认对非ASCII字符做Unicode转义这是符合JSON规范的不是乱码。前端拿到的如果是字符串形式需要自己decode才能显示中文。解决PHP里输出给页面时加JSON_UNESCAPED_UNICODEecho json_encode($results, JSON_UNESCAPED_UNICODE);逻辑说明这个参数只影响输出形态不影响数据结构。做API接口时保留默认转义也不算错做HTML页面输出时必须加这个参数否则用户看到的就是一串转义符。这个坑从PHP 5.x时代就存在到现在仍有大量v1.0源码没处理。6. 把一次搜索变成一项搜索能力日志、降级与可信度校验API能通只是及格线生产上真正要看的是请求有记录、失败有兜底、结果可信。先做日志。把每次搜索的关键词、耗时、HTTP状态码写进logs目录用追加写方便按天轮转。日志格式用JSON后面接日志采集系统不用改结构。搜索结果本身不落日志体积太大只记元信息。$log [ time date(c), q $q, http_code $code ?? null, cost_ms round((microtime(true) - $t0) * 1000), ]; file_put_contents($logFile, json_encode($log, JSON_UNESCAPED_UNICODE) . PHP_EOL, FILE_APPEND);逻辑说明追加写是为了并发场景下多条日志不互相覆盖FILE_APPEND让每条记录落在独立一行按天轮转时用文件名区分即可。日志字段里不存结果体避免磁盘被摘要文本塞满。再谈降级。API配额用完或网络故障时按缓存优先、API次之、HTML抓取兜底的顺序走。注意HTML抓取只是兜底不能当主链路。下面这个三档降级写法是我给这类源码补的最后一层保险$data cache_get($q); if ($data null) { try { $data api_search($q); } catch (Throwable $e) { $data html_fallback($q); // 最后一档兜底 } cache_set($q, $data, 1800); }逻辑说明cache_get和cache_set是前面缓存逻辑的函数化封装html_fallback对应上面2.3节那段HTML解析逻辑。顺序不能反HTML抓取放最后一档因为它既不省心也容易被中间页拦截。最后说结果可信度。必应返回的URL偶尔会有跳转链展示前做一层host白名单校验能挡住一部分低质站点$allowed [php.net, github.com, stackoverflow.com]; $safe array_filter($items, function ($item) use ($allowed) { return in_array(parse_url($item[url], PHP_URL_HOST), $allowed); });逻辑说明parse_url取出hostin_array匹配白名单不在名单里的结果不进渲染数组。上线前还可以用命令行验证整条链路是否真实可用这一步可以写进发布脚本KEY你的Key curl -s -H Ocp-Apim-Subscription-Key: $KEY \ https://api.bing.microsoft.com/v7.0/search?qphpcount1 | \ php -r $djson_decode(stream_get_contents(STDIN),true); echo $d[webPages][value][0][url] ?? no result;逻辑说明命令行验证的好处是绕开PHP-FPM运行环境直接确认Key、endpoint、网络链路。输出一个URL表示链路通输出no result则先查参数再查代码。这类搜索源码的核心不在搜索本身而在别让外部接口的抖动传导到自己的页面。我自己的习惯是任何Bing搜索源码到手先跑一次最小调用再补日志和缓存最后才谈样式。把这三件套做完v1.0才真正能上生产。希望帮到你。本文还有配套的精品资源点击获取