目标网站:http://www.yidianzixun.com/
抓取范围:各频道页面下的数据
抓包,找到数据接口:http://www.yidianzixun.com/home/q/news_list_for_channel?channel_id=21422801782&cstart=0&cend=10&infinite=true&refresh=1&__from__=pc&multi=5&_spt=yz~eaod8%3B>882%3A%3B%3D28%3A%3B%3A&appid=web_yidian&_=1557501340007
参数说明:
请求该链接时,未正常返回数据,所以将headers全部拷贝,再次请求,有数据,多次尝试后,确定headers中必须的参数:
全局搜索_spt参数,找到js文件:http://static.yidianzixun.com/modules/build/index_pc/channel-6502fbb6.js
生成_spt的js代码如下:
void 0 !== (t = function(n, e, i) { e.encodeToken = function(n, e, i, t) { for (var o = "sptoken", a = "", c = 1; c < arguments.length; c++) o += arguments[c]; for (var c = 0; c < o.length; c++) { var r = 10 ^ o.charCodeAt(c); a += String.fromCharCode(r) } return n += (/\?/.test(n) ? "&_spt=" : "?_spt=") + encodeURIComponent(a) }
通过断点调试,确定arguments的参数构成:“sptoken” + channel_id + cstart + cend,通过字符和unicode码转换生成对应的_spt参数
def get_spt(id, start=0, end=10): str_par = 'sptoken{}{}{}'.format(id,start, end) spt = '' for key in str_par: spt += chr(10^ord(key)) return spt
清空缓存后,全局搜索JSESSIONID对应的值,在请求频道对应的链接(如首页:http://www.yidianzixun.com/ )时,返回cookie中存在该值
同时多次测试,发现每次清空缓存后,刷新页面,获取的JSESSIONID不同,同时同一频道对应的channel_id每次也都不同,因此判断JSESSIONID与channel_id存在对应关系,且一次请求可以获得一个JSESSIONID和对应的28个频道的channel_id
import json import re import time import requests def get_spt(id, start=0, end=10): str_par = 'sptoken{}{}{}'.format(id,start, end) spt = '' for key in str_par: spt += chr(10^ord(key)) return spt if __name__ == '__main__': ids = set() url = 'http://www.yidianzixun.com/' headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', } while True: response = requests.get(url, headers=headers, timeout=15) if response.headers.get('Set-Cookie'): Cookies = response.headers.get('Set-Cookie').split(';') for cookie in Cookies: if re.findall(r'JSESSIONID=(.+)', cookie, re.S): set_cookie = re.findall(r'JSESSIONID=(.+)', cookie, re.S)[0] else: print('----获取cookies失败') time.sleep(1) continue data_json = re.findall(r'window.yidian.docinfo.*?(\{.*?)</script>', response.text, re.S)[0].replace('\\x2f', '') data_dict = json.loads(data_json) channel_list = data_dict['user_info']['user_channels'] for j in range(5): for channel in channel_list: id = channel.get('fromId') if not id: print('no id') continue offset = 30 start, end = 0, offset for i in range(300): list_url = 'http://www.yidianzixun.com/home/q/news_list_for_channel?channel_id={}&cstart={}&cend={}&infinite=true&refresh=1&__from__=pc&multi=5&_spt={}&appid=web_yidian&_={}'.format(id, start, end, get_spt(id, start, end), int(time.time()*1000)) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36', 'Referer': 'http://www.yidianzixun.com/channel/c3', 'Cookie': 'JSESSIONID={}'.format(set_cookie) } response = requests.get(list_url, headers=headers, timeout=15) res_dict = json.loads(response.text) if isinstance(res_dict, str): print('---返回结果为字符串, response:{}'.format(res_dict)) continue if res_dict.get('reason'): print('-----抓取完成, resonse:{}, channel:{},data_list:{}, ids:{}, pg:{}, start:{}, end:{}'.format(res_dict.get('reason'), channel.get('name'), len(data_list), len(ids), i, start, end)) break try: data_list = res_dict['result'] except: print(res_dict) continue for data in data_list: if data['itemid'] not in ids: ids.add(data['itemid']) time.sleep(0.5) print('-----channel:{},data_list:{}, ids:{}, pg:{}, start:{}, end:{}'.format(channel.get('name'), len(data_list), len(ids), i, start, end)) start, end = start+len(data_list), start+len(data_list)+offset