今日头条爬虫解析
如今自媒体风生水起,很多人开始入住各大自媒体平台进行自媒体创作。想要持续的输出高质量的文章太难了,于是很多人就开始搞起了伪原创,拿别人比较热的文章过来改一改,不仅轻松还能收获一票粉丝,偏题了,我不是鼓励大家搞伪原创。今天我们的主题是爬虫,既然自媒体平台有这么多高质量的文章,想要一一收藏太难了,于是就想出了通过网络爬虫将感兴趣的文章爬取下来然后进行收藏,岂不是很爽,于是开始动手。今天拿今日头条练手。
第一步分析入口,今日头条的文章入口主要分为频道入口,搜索入口,用户主页入口,那么我们就一一开始破解。
首先从频道入口下手,分析网页结构,发现所有的文章都是通过ajax动态加载,那么第一想法是通过selenium模拟浏览器进行网页提取,虽然目的可以达到但是效果不理想,需要处理分页去重而且效率不高,故这个方案放弃,在接着进行分析ajax接口,貌似行得通,架起代码开始测试,在处理ajax接口是发现需要处理as,cp,_signature 三个参数,接口如下
as,cp网上已经有大神破解了算法如下;
def getASCP(self):
t = int(math.floor(time.time()))
e = hex(t).upper()[2:]
m = hashlib.md5()
m.update(str(t).encode(encoding='utf-8'))
i = m.hexdigest().upper()
if len(e) != 8:
AS = '479BB4B7254C150'
CP = '7E0AC8874BB0985'
return AS, CP
n = i[0:5]
a = i[-5:]
s = ''
r = ''
for o in range(5):
s += n[o] + e[o]
r += e[o + 3] + a[o]
AS = 'A1' + s + e[-3:]
CP = e[0:3] + r + 'E1'
return AS, CP
比较难搞的是signature参数,虽然网上也有很多人发文怎么破解,但是效果都不理想,只能拿到第一次请求的signature,第二次请求就直接不行了。分析js发现signature的请求是通过window.TAC.sign方法产生,而这个方法是动态绑定的,由一堆看不清逻辑的字符串通过一定的算法解密后得到,着手分析了一下,发现里面用到了Date ,Convas 相关的函数,姑且进行了一下推断,判断入参的时间戳跟当前的时间戳进行对比,肯定不能大于当前的时间,但是范围也不能相差太远否则一个频道页内容直接就爆了,对于Convas 可能就是为了限定上下文,否则为什么我们通过python直接执行TAC.sign方法产生的signature在第二次就会失效,虽说第一次能请求成功那么我们是不是每次都模拟第一次请求,虽说也能得到数据,但不能连续采集还是有点失望的。那有没有其他的办法可以解决了,在经过一下午的思考过后发现,既然在保证上下文的情况下可以连续采集(通过chrome的console生成的signature是可以的)那么我们通过selenium方案来模拟上下文,负责产生signature,剩下的就是通过urllib来请求接口获取数据进行解析是否可以行了?经过一番验证结果是可喜的,功夫不负有心人达到目的。直接贴代码。代码中有些自有逻辑没有贴出来,但是逻辑思路基本上都有了。今天到此为止,下次说怎么爬详情
def get_channel_data(self, page): #获取数据
req = self.s.get(url=self.url, verify=False, proxies=get_proxy_ip())
#print (self.s.headers)
#print(req.text)
headers = {'referer': self.url}
max_behot_time='0'
signature='.1.hXgAApDNVcKHe5jmqy.9f4U'
eas = 'A1E56B6786B47FE'
ecp = '5B7674A7FF2E9E1'
self.s.headers.update(headers)
item_list = []
browser = webdriver.Chrome()
browser.implicitly_wait(10)
browser.get(self.url)
for i in range(0, page):
Honey = json.loads(self.get_js())
# eas = self.getHoney(int(max_behot_time))[0]
# ecp = self.getHoney(int(max_behot_time))[1]
eas = Honey['as']
ecp = Honey['cp']
signature = Honey['_signature']
if i > 0:
signature = browser.execute_script("return window.TAC.sign("+ max_behot_time +")")
url='https://www.toutiao.com/api/pc/feed/?category={}&utm_source=toutiao&widen=1&max_behot_time={}&max_behot_time_tmp={}&tadrequire=true&as={}&cp={}&_signature={}'.format(self.channel,max_behot_time,max_behot_time,eas,ecp,signature)
req=self.s.get(url=url, verify=False, proxies=get_proxy_ip())
time.sleep(random.random() * 2+2)
# print(req.text)
# print(url)
j=json.loads(req.text)
for k in range(0, 10):
item = toutiaoitem()
now=time.time()
if j['data'][k]['tag'] != 'ad' or j['data'][k]['tag'] != 'ad.platform.site':
item.title = j['data'][k]['title'] ##标题
item.source = j['data'][k]['source'] ##作者
item.source_url = 'https://www.toutiao.com/'+j['data'][k]['source_url'] ##文章链接
item.media_url = 'https://www.toutiao.com/'+j['data'][k]['media_url'] #作者主页
item.article_genre = j['data'][k]['article_genre'] #文章类型
try:
item.comments_count = j['data'][k]['comments_count'] ###评论
except:
item.comments_count = 0
item.tag = j['data'][k]['tag'] ###频道名
try:
item.chinese_tag = j['data'][k]['chinese_tag'] ##频道中文名
except:
item.chinese_tag = ''
try:
item.label = j['data'][k]['label'] ## 标签
except:
item.label = []
try:
item.abstract = j['data'][k]['abstract'] ###文章摘要
except:
item.abstract = ''
behot = int(j['data'][k]['behot_time'])
item.behot_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(behot)) ####发布时间
item.collect_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(now)) ##抓取时间
item.item_id = j['data'][k]['item_id']
try:
item.image_list = j['data'][k]['image_list']
except:
item.image_list = []
item.image_url = j['data'][k]['image_url']
item.middle_image = j['data'][k]['middle_image']
item_list.append(item)
toutiaodb.save(item_list)
time.sleep(2)
max_behot_time = str(j['next']['max_behot_time'])