前言
之所以想做这个 API,主要是我博客上有百度收录检测,但是速度有点慢,后来想想可能是因为服务器在国外,所以就有了把 API 搬到国内的想法。
但是国内的 API 已经用 python 搭建了一个随机图服务,不好再加一个 PHP 吧?算了,干脆我重写一个 API 得了,于是就有了这个项目。代码已经放到 GitHub 上了,欢迎大家交流讨论。
[scode type="yellow"]查询次数过于频繁有大概率会查询失败,目前只是半成品,只适合自用[/scode]
[scode type="lblue"]源码里有其他 API,大家直接看 get_baidu
即可[/scode]
Demo:https://api.yanshu.work/baidu/?u=https://www.sitstars.com
思路
原作者检测的方法貌似是直接查询网址,然后匹配有多少搜索结果几条,如果搜索结果大于 0 就认为已收录。但是这种方法有个问题,百度有时候会收录一些与目标网址无关的页面,这样检测会不准确。
网上搜了搜,其他一些文章是查询百度跳转链接,然后一个个获取真实链接,再与查询的网址进行匹配,如果能匹配上就说明已收录。嗯想法很好,理论上准确性非常高,就是有点耗时间……不管怎么样总归是个方案,先试试。
很遗憾,技术有限,我并不能获取百度跳转后的真实链接,Stack Overflow 上给出的解决方法也统统不起作用,这个方案宣告失败。
PS:记录下获取跳转后真实链接的几种方法,说不定以后会用上。
# 方法一
r = requests.get('http://techtv.mit.edu/videos/1585-music-session-02/download.source')
print(r.url) # http://d1baxxa0joomi3.cloudfront.net/2515a9db659b0ab26d869b4ff2dadca9/original.mov
# 方法二:这种适用于多次跳转
response = requests.get(someurl)
if response.history:
print("Request was redirected")
for resp in response.history:
print(resp.status_code, resp.url)
print("Final destination:")
print(response.status_code, response.url)
else:
print("Request was not redirected")
# 方法三
r = requests.get('http://github.com/', allow_redirects=False)
r.status_code # 302
r.url # http://github.com, not https.
r.headers['Location'] # https://github.com/ -- the redirect destination
# 方法四
import urllib.request
res = urllib.request.urlopen(starturl)
finalurl = res.geturl()
print(finalurl)
参考资料:https://stackoverflow.com/questions/20475552/python-requests-library-redirect-new-url
分析
只能一步步分析了,首先看个已收录的文章:
再看看没有收录的文章:
突然有了想法,我直接解析 <b>url<b>
不就行了?如果能匹配到就说明已收录,匹配不到就没有收录。顺着这个思路,很快写好了第一版代码。
def get_baidu(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
}
baidu = 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd='
search_url = baidu + url
response = requests.get(search_url,headers=headers)
text = response.text
if 'http' in url:
url = url[url.index('//')+2:]
pattern=re.compile(r'<b>{}</b>'.format(url))
result_list = pattern.findall(text)
if result_list:
return 200
else:
return 403
随便测试了几篇文章,确实有效,可以收工了……且慢,再找找其他人的文章测试一下吧。
输入 http://www.ruanyifeng.com/blog/2017/12/blockchain-tutorial.html,嗯?怎么返回 403 了?用百度搜了一下,没错啊,已经收录了,而且搜索出来的结果也是加粗的。
仔细看了一下,哦……原来百度把太长的网址隐藏了一部分啊,所以匹配不到。这就难办了,诚然我可以截取网址的一部分进行匹配,但是这样就又不精确了。
我又仔细观察了一下,发现如果百度确实收录了,那么这个网址会在搜索结果的最上方,而且第一条和第二条搜索结果之间会有“以下是网页中包含……”的提示信息。于是我有了个新的想法,首先截取网页文本从开头到“以下是网页中……”的内容,然后用正则匹配百度跳转链接,如果能匹配得到说明百度第一条有搜索到的网页结果,那这篇文章肯定收录了。于是我写出了第二版代码。
@app.get("/baidu/")
async def get_baidu(u: str = Query(..., min_length=3)):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36',
'Referer': 'https://www.baidu.com'
}
# 网址后缀处理
if '.' in u[-5:]:
pass
elif u[-1] != '/':
u += '/'
# 网址协议头处理
if ('http' in u) and not ('www' in u):
u = 'www.' + u[u.index('//')+2:]
elif ('http' not in u) and not ('www' in u):
u = 'www.' + u
baidu = 'https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=1&tn=baidu&wd='
search_url = baidu + u
try:
response = requests.get(search_url, headers=headers)
text = response.text
text = text[:text.index('以下是网页中包含')]
pattern = re.compile(r'"http://www.baidu.com/link\?url=.+?"')
result_list = pattern.findall(text)
if result_list:
return {'code': 200, 'msg': '该网址已被百度收录!'}
else:
return {'code': 403, 'msg': '该网址暂未被百度收录!'}
except:
return {'code': 404, 'msg': '百度收录查询失败!'}
嗯,很好,这次是真的可以收工了……(Flag 预定)。
PS:可以看到代码里有个网址的预处理,这是因为网址没有最后一个 /
时会查不出来,但是 .html
这种格式的又不需要加。另外,网址开头可以不带 http/https
,但是必须带 www
,要不然同样搜不出来。
跨域问题
测试没问题,那就可以上线了,部署到服务器上,按照 https://www.sitstars.com/archives/65/中的教程修改 API 地址,看下情况。问题又来了……
API 请求地址出现了一个鲜红的红色,提示 strict-origin-when-cross-origin
错误,看样子是跨域错误?试着用错误提示 +fastapi
在 Google 上搜了一下,果然 fastapi 早已经考虑到了这个问题,并且给出了解决方案。
参考资料:https://fastapi.tiangolo.com/tutorial/cors/
简单来说,加入以下代码就可以了:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"http://localhost.tiangolo.com",
"https://localhost.tiangolo.com",
"http://localhost",
"http://localhost:8080",
"*"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
origins
是一个所有允许的请求域列表,加入 *
代表允许所有请求,当然也可以加入具体的网址和端口,比如上面几个例子。
加好后再测试一下,成功显示!而且速度还挺快。
百度自身问题
正当我开心地刷新网站看效果时,悲剧了,网页上突然提示我百度收录查询失败!这是为何?手动试了一下 API,果然输出不了结果,但是本地使用又完全没有问题。莫非是我搜得过于频繁导致 ip 被百度拉黑了?于是我找了很多种解决方案,比如 headers
添加更多参数,更换代理 ip,完全没用!
没办法我又看了原作者的博客,注意到 PHP 版本 API 的代码里有个 CURLOPT_FOLLOWLOCATION
,百度一下发现它的功能是跟随网址的重定向。嗯……会不会是服务器搜索太快导致百度重定向没反应过来?于是我借助第二节的代码,print()出最终跳转后的网址,看看它到底给我定向到哪了。
修改好代码后再次部署,没想到这次 API 又有用了,看来是抽风性质的问题。刷新了几次又不能用了,而此时服务器的控制台上也刷出了重定向后的网址,和我拼接的百度查询网址一样!这又是什么情况?我自身对网络方面知之甚少,所以最终也没有解决方案,只是知道了判断百度查询失败的另一个方法:只要有重定向那就一定失败了。
后记
这玩意搞了我一个晚上,最终还是放弃了,留了一个 GitHub 上的半成品,本来想部署好后开放给大家用的,但是现在只能先自用了。大家有兴趣的话可以自行部署,或者留言我把你的域名添加到跨域列表里。
接下来我的计划是加入缓存,请求成功后存储到本地,一段时间内只从本地 读取结果,防止每次打开文章都要请求一次百度。
版权属于:作者名称
本文链接:https://www.sitstars.com/archives/110/
转载时须注明出处及本声明
挺好好用的,希望完善下PHP代码
风 2021-06-25
http://www.link114.cn/baidusl/ 可以试试直接爬这个网站的接口,一次可查500条,也就是免费+无限使用了
Request URL: http://www.link114.cn/multi.php
Request Method: POST
func=baidu_checksl_pc|baidu_checksl_wap&websites=
tuziang 2021-01-31
感谢分享!
雁陎 2021-02-01 回复 @tuziang