10月29日搜狗微信改版了,无法通过搜索公众号名字获取对应文章了,不过通过搜索主题获取对应文章还是可以的
目的:获取搜狗微信中搜索主题返回的文章。
涉及反爬机制:cookie设置,js加密。
按照正常的采集流程,此时按F12打开浏览器的开发者工具,利用选择工具点击列表中文章标题,查看源码中列表中文章url的所在位置,再用xpath获取文章url的值,也就是这个href的值,为避免混乱,我们称之为“列表页面的文章url”。
可以看到“列表页面的文章url”需要拼接,一般这种情况需要在浏览器中正常访问一下这篇文章,对比观察跳转后的url(我们称之为“真实的文章url”),再缺头补头缺脚补脚即可。下面是两个url的对比:
列表中的URL:
/link?url=dn9a_-gY295K0Rci_xozVXfdMkSQTLW6cwJThYulHEtVjXrGTiVgSxXWO5jMDx5JEf8m2C7b5SMTWh5L4nxL0VqXa8Fplpd9WUVPGtaUij32IWbSS4PlqhHR25kJ9aGxdoUK5-HQJHSBUdf-_CsD_hzCsZ6RhgCTjzjsLxbGyyz3rRO5tNfFYA0Do8PmX4rKqC0uMe50XyeKPeFRq4j4_a_ZPEIMWLv_k45Jm2PIBwT5mKmu2NYgixeplxJSDXjNAcSZfw2J2-OAFV_3u-a3OQ..&type=2&query=%E5%BE%90%E6%82%B2%E9%B8%BF%E5%A5%B3%E5%84%BF%E5%8E%BB%E4%B8%96
真实的URL:
这里很明显两个url的路径不一致,应该是中间经过了一些调转,python的requests库是带自动调转功能,我们先把域名https://mp.weixin.qq.com补上试一下访问
直接点击列表URL,会被拦截,让输出验证码,明显这里做了反爬限制,那么这里开始,我们就需要抓包分析了。这里用到的工具是Firefox浏览器的开发者工具。抓包观察的是从搜索结果页面列表文章点击跳转到文章页面的过程,这里点击文章超链接会在新窗口打开,我们只需要在网页源码中把对应a标签的target属性改为空,就可以在一个窗口中观察整个流程的数据包了。
fiddle抓包工具分析:
通过抓包我们可以找到搜索结果页面跳转到文章页面的过程,这里观察发现,“列表页面的文章url”返回的结果中就包含了“真实的文章url”的信息,这意味着我们只需要正确访问到“列表页面的文章url”,根据返回的数据就能拼接出“真实的文章url”并访问了,这样我们就实现从“列表页面的文章url”到“真实的文章url”的跳转了!
抓包分析手动获取的“列表也米娜的文章URL”无法访问
此时我们的目标就从获取“真实的文章url”转变到正确的访问“列表页面的文章url”了,继续分析抓包数据中的“列表页面的文章url”信息:
抓包数据:
mothod:GET
/link?url=dn9a_-gY295K0Rci_xozVXfdMkSQTLW6cwJThYulHEtVjXrGTiVgSxXWO5jMDx5JEf8m2C7b5SMTWh5L4nxL0VqXa8Fplpd9WUVPGtaUij32IWbSS4PlqhHR25kJ9aGxdoUK5-HQJHSBUdf-_CsD_hzCsZ6RhgCTjzjsLxbGyyz3rRO5tNfFYA0Do8PmX4rKqC0uMe50XyeKPeFRq4j4_a_ZPEIMWLv_k45Jm2PIBwT5mKmu2NYgixeplxJSDXjNAcSZfw2J2-OAFV_3u-a3OQ..&
type=2&
query=%E5%BE%90%E6%82%B2%E9%B8%BF%E5%A5%B3%E5%84%BF%E5%8E%BB%E4%B8%96&
k=69&
h=F
headers:
GET /link?url=dn9a_-gY295K0Rci_xozVXfdMkSQTLW6cwJThYulHEtVjXrGTiVgSxXWO5jMDx5JEf8m2C7b5SMTWh5L4nxL0VqXa8Fplpd9WUVPGtaUij32IWbSS4PlqhHR25kJ9aGxdoUK5-HQJHSBUdf-_CsD_hzCsZ6RhgCTjzjsLxbGyyz3rRO5tNfFYA0Do8PmX4rKqC0uMe50XyeKPeFRq4j4_a_ZPEIMWLv_k45Jm2PIBwT5mKmu2NYgixeplxJSDXjNAcSZfw2J2-OAFV_3u-a3OQ..&type=2&query=%E5%BE%90%E6%82%B2%E9%B8%BF%E5%A5%B3%E5%84%BF%E5%8E%BB%E4%B8%96&k=69&h=F HTTP/1.1
Host: weixin.sogou.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: https://weixin.sogou.com/weixin?type=2&ie=utf8&s_from=hotnews&query=%E5%BE%90%E6%82%B2%E9%B8%BF%E5%A5%B3%E5%84%BF%E5%8E%BB%E4%B8%96
Cookie:
ABTEST=0|1574763082|v1;
IPLOC=CN3100;
SUID=B535A9B43E18960A000000005DDCFA4A;
SUID=B535A9B42B12960A000000005DDCFA4A;
weixinIndexVisited=1;
SUV=006B15E7B4A935B55DDCFA4AE52C6946;
SNUID=75F46975C0C559893A3FC125C16C5243;
sct=5;
JSESSIONID=aaa8EnASm4RZA51Sj2y4w
Upgrade-Insecure-Requests: 1
这里的重点有三个:
请求参数:对比我们获取的“列表页面的文章url”分析可以发现,这里多了两个参数“k”、“h”,这是需要我们设法获取的。headers:经过测试该网站对User-Agent敏感,一次访问前后User-Agent需要一致。
Cookie:Cookie中参数需要获取才能正确访问该url。这些参数分别是:ABTEST、IPLOC、JSESSIONID、SNUID、SUID、SUV。
获取参数“k”、“h”
按照经验,从一个url转变成另一个url有两种情况:跳转和javascript字符串处理。经过多次抓包分析发现,搜索结果页面点击文章超链接到我们现在的目标url并没有存在跳转情况,抓包数据中的“列表页面的文章url”和我们获取的“列表页面的文章url”可以判定为同一个url,所以猜测为javascript字符串处理。经过一番搜寻,发现搜索结果页面的源码中有一段非常可疑的代码:
这其中最重要的代码就是:this.href+="&k="+b+"&h="+a,这代码就是在点击事件发生时给a标签href属性的内容添加"&k="、"&h=",正是用这段代码对该url的参数进行js加密和添加的。我们只需要把这段代码用python实现就可以解决这个问题了,下面是实现python实现代码
def get_k_h(url):
b = int(random.random() * 100) + 1
a = url.find("url=")
url = url + "&k=" + str(b) + "&h=" + url[a + 4 + 21 + b: a + 4 + 21 + b + 1]
reuturn url
获取Cookie的参数
观察抓包数据可以发现,当我们一开始访问时并没有带任何cookie,但经过一系列请求,到我们的目标请求时候,浏览器已经通过前面请求的返回数据包的Set-Cookie属性把Cookie构造出来了,而我们要做的就是在Cookie构造从无到有这个过程中找到所有ResponseHeaders中带SetCookie属性的而且参数是我们需要的参数的请求,并模拟访问一遍,就能得到所有参数并构建出我们需要的Cookie了。
例如搜狗微信搜索接口的请求的ResponseHeaders就有5个Set-Cookie字段,其中ABTEST、SNUID、IPLOC、SUID都是我们最终构造Cookie所需的参数(和最后的Cookie值对比可以发现,这里的SUID值还不是我们最终需要的,要在后面的数据包中继续发掘)。
经过分析,经过四个请求获取到的ResponseHeaders后我们就能正确构建Cookie了:
1. 得到ABTEST、SNUID、IPLOC、SUID:
https://weixin.sogou.com/weixin?type=2&query=%E5%92%B8%E8%9B%8B%E8%B6%85%E4%BA%BA&ie=utf8&s_from=input&_sug_=n&_sug_type_=1&w=01015002&oq=&ri=1&sourceid=sugg&sut=750912&sst0=1573092594229&lkt=0%2C0%2C0&p=40040108
2. 需要IPLOC、SNUID,得到SUID:
https://www.sogou.com/sug/css/m3.min.v.7.css
3. 需要ABTEST、IPLOC、SNUID、SUID,得到JSESSIONID:
https://weixin.sogou.com/websearch/wexinurlenc_sogou_profile.jsp
4. 需要IPLOC、SNUID、SUID,得到SUV
https://pb.sogou.com/pv.gif
这四个请求都能根据前面请求获取到的Cookie参数来构造自己需要的Cookie去正确访问。值得注意的是最后一个请求,除了需要正确拼接Cookie外,还需要获取正确的请求参数才能正常访问:
那么根据这些解析出来的参数和前面三个请求得到的Cookie参数就能正确访问第四个请求并得到所需的所有Cookie参数啦!
构造正确的请求信息
此时,我们已经分析出所有正确模拟请求的流程了,梳理一下:
- 获取“k”、“h”参数,传入搜索结果页面得到的“列表页面的文章ur”,调用get_k_h()即可。
- 获取所需Cookie参数,构造正确的Cookie,按照流程三给出的4个url,分别构造请求获取ResponseHeaders中的SetCookie即可。
- 构造正确的请求访问“列表页面的文章url”。
- 根据3中请求返回的数据,拼接出“真实的文章url”,也就是流程二。
- 请求“真实的文章url”,得到真正的文章页面数据。
至此,所有分析结束,可以愉快的码代码啦!
结语:此次采集涉及到的反爬技术是Cookie构造和简答的js加密,难度不大,最重要的是耐心和细心。此外提醒各位看客大人遵循爬虫道德,不要对他人网站造成伤害,peace!
import random
from urllib import parse
import requests
from lxml import etree
import re
import json
import urllib3
urllib3.disable_warnings()
# 获取查询请求所需的K 和 h参数
# function() {
# var b = Math.floor(100 * Math.random()) + 1,
# a = this.href.indexOf("url="),
# c = this.href.indexOf("&k=");
# - 1 !== a && -1 === c && (a = this.href.substr(a + 4 + parseInt("21") + b, 1), this.href += "&k=" + b + "&h=" + a)
# }
def get_k_h(url):
b = int(random.random() * 100) + 1
a = url.find("url=")
url = url + "&k=" + str(b) + "&h=" + url[a + 4 + 21 + b:a + 4 + 21 + b + 1]
return url
def log(content):
with open(r"F:\个人总结\log\sgwx.log", "a+", encoding="utf-8") as file:
file.write(str(content) + "\n")
def get_proxy():
for i in range(20):
print("get proxy try time:", i + 1)
proxy_url = requests.get(
"http://svip.kdlapi.com/api/getproxy/?orderid=936273988936860&num=1&protocol=1&method=2&an_an=1&an_ha=1&quality=2&sep=1").text
proxy = {"https": "https://%s" % proxy_url}
try:
requests.get("https://www.baidu.com/", proxies=proxy, timeout=5)
print(proxy)
return proxy
except:
continue
return None
def get_uigs_para(response):
log("从查询响应页面获取uigs_para")
uigs_para = re.findall('var uigs_para = (.*?);', response.text, re.S)[0]
if 'passportUserId ? "1" : "0"' in uigs_para:
uigs_para = uigs_para.replace('passportUserId ? "1" : "0"', '0')
uigs_para = json.loads(uigs_para)
exp_id = re.findall('uigs_para.exp_id = "(.*?)";', response.text, re.S)[0]
uigs_para['right'] = 'right0_0'
uigs_para['exp_id'] = exp_id[:-1]
log(uigs_para)
return uigs_para
def get_cookie(response, uigs_para, UserAgent):
SetCookie = response.headers['Set-Cookie']
cookie_params = {
"ABTEST": re.findall('ABTEST=(.*?);', SetCookie, re.S)[0],
"SNUID": re.findall('SNUID=(.*?);', SetCookie, re.S)[0],
"IPLOC": re.findall('IPLOC=(.*?);', SetCookie, re.S)[0],
"SUID": re.findall('SUID=(.*?);', SetCookie, re.S)[0]
}
log("获取查询后页面的响应headers:{}".format(cookie_params))
url = "https://www.sogou.com/sug/css/m3.min.v.7.css"
headers = {
"Accept": "text/css,*/*;q=0.1",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Connection": "keep-alive",
"Cookie": "SNUID={}; IPLOC={}".format(cookie_params['SNUID'], cookie_params['IPLOC']),
"Host": "www.sogou.com",
"Referer": "https://weixin.sogou.com/",
"User-Agent": UserAgent
}
response_css = requests.get(url, headers=headers, verify=False)
SetCookie = response_css.headers['Set-Cookie']
cookie_params['SUID'] = re.findall('SUID=(.*?);', SetCookie, re.S)[0]
log("请求m3.min.v.7.css之后获取headers:{}".format(cookie_params))
url = "https://weixin.sogou.com/websearch/wexinurlenc_sogou_profile.jsp"
headers = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Connection": "keep-alive",
"Cookie": "ABTEST={}; SNUID={}; IPLOC={}; SUID={}".format(cookie_params['ABTEST'], cookie_params['SNUID'],
cookie_params['IPLOC'],
cookie_params['SUID']),
"Host": "weixin.sogou.com",
"Referer": response.url,
"User-Agent": UserAgent
}
response_jps = requests.get(url, headers=headers, verify=False)
SetCookie = response_jps.headers['Set-Cookie']
cookie_params['JSESSIONID'] = re.findall('JSESSIONID=(.*?);', SetCookie, re.S)[0]
log("获取wexinurlenc_sogou_profile.jsp的headers:{}".format(cookie_params))
url = "https://pb.sogou.com/pv.gif"
headers = {
"Accept": "image/webp,*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Connection": "keep-alive",
"Cookie": "SNUID={}; IPLOC={}; SUID={}".format(cookie_params['SNUID'], cookie_params['IPLOC'],
cookie_params['SUID']),
"Host": "pb.sogou.com",
"Referer": "https://weixin.sogou.com/",
"User-Agent": UserAgent
}
response_gif = requests.get(url, headers=headers, params=uigs_para, verify=False)
SetCookie = response_gif.headers['Set-Cookie']
cookie_params['SUV'] = re.findall('SUV=(.*?);', SetCookie, re.S)[0]
log("获取pv.gif的headers:{}".format(cookie_params))
return cookie_params
def main_page(list_url, UserAgent):
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Connection": "keep-alive",
"Host": "weixin.sogou.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": UserAgent,
}
# 请求查询
response = requests.get(url=list_url, headers=headers, verify=False)
log("查询后的response headers:{}".format(response.headers))
html = etree.HTML(response.text)
# 获取查询到的文章链接
urls = ['https://weixin.sogou.com' + i for i in html.xpath('//div[@class="img-box"]/a/@href')]
uigs_para = get_uigs_para(response)
params = get_cookie(response, uigs_para, UserAgent)
approve_url = 'https://weixin.sogou.com/approve?uuid={}'.format(uigs_para['uuid'])
headers2 = {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Connection": "keep-alive",
"Cookie": "ABTEST={}; IPLOC={}; SUID={}; SUV={}; SNUID={}; JSESSIONID={};".format(params['ABTEST'],
params['IPLOC'],
params['SUID'], params['SUV'],
params['SNUID'],
params['JSESSIONID']),
"Host": "weixin.sogou.com",
"Referer": response.url,
"User-Agent": UserAgent,
"X-Requested-With": "XMLHttpRequest"
}
for url in urls:
log("https://weixin.sogou.com/approve?uuid=")
requests.get(approve_url, headers=headers2, verify=False)
url = get_k_h(url)
headers3 = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
"Connection": "keep-alive",
"Cookie": "ABTEST={}; SNUID={}; IPLOC={}; SUID={}; JSESSIONID={}; SUV={}".format(params['ABTEST'],
params['SNUID'],
params['IPLOC'],
params['SUID'],
params['JSESSIONID'],
params['SUV']),
"Host": "weixin.sogou.com",
"Referer": list_url,
"Upgrade-Insecure-Requests": "1",
"User-Agent": UserAgent
}
response3 = requests.get(url, headers=headers3, verify=False)
fragments = re.findall("url \+= '(.*?)'", response3.text, re.S)
itemurl = ''
for i in fragments:
itemurl += i
# 文章url拿正文
headers4 = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"accept-encoding": "gzip, deflate, br",
"accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
"cache-control": "max-age=0",
"user-agent": UserAgent
}
response4 = requests.get(itemurl, headers=headers4, verify=False)
html = etree.HTML(response4.text)
print(response4.url)
print(response4.status_code)
print(html.xpath('//meta[@property="og:title"]/@content')[0])
if __name__ == '__main__':
keys = "具荷拉身亡"
page = 1
UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:64.0) Gecko/20100101 Firefox/64.0"
url = "https://weixin.sogou.com/weixin?type=2&s_from=input&query={}&_sug_=n&_sug_type_=&page={}".format(
parse.quote(keys), page)
print("搜索生成连接:", url)
main_page(url, UserAgent)