如今自媒體風生水起,很多人開始入住各大自媒體平臺進行自媒體創(chuàng)作。想要持續(xù)的輸出高質量的文章太難了,于是很多人就開始搞起了偽原創(chuàng),拿別人比較熱的文章過來改一改,不僅輕松還能收獲一票粉絲,偏題了,我不是鼓勵大家搞偽原創(chuàng)。今天我們的主題是爬蟲,既然自媒體平臺有這么多高質量的文章,想要一一收藏太難了,于是就想出了通過網絡爬蟲將感興趣的文章爬取下來然后進行收藏,豈不是很爽,于是開始動手。今天拿今日頭條練手。
第一步分析入口,今日頭條的文章入口主要分為頻道入口,搜索入口,用戶主頁入口,那么我們就一一開始破解。
首先從頻道入口下手,分析網頁結構,發(fā)現(xiàn)所有的文章都是通過ajax動態(tài)加載,那么第一想法是通過selenium模擬瀏覽器進行網頁提取,雖然目的可以達到但是效果不理想,需要處理分頁去重而且效率不高,故這個方案放棄,在接著進行分析ajax接口,貌似行得通,架起代碼開始測試,在處理ajax接口是發(fā)現(xiàn)需要處理as,cp,_signature 三個參數(shù),接口如下
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參數(shù),雖然網上也有很多人發(fā)文怎么破解,但是效果都不理想,只能拿到第一次請求的signature,第二次請求就直接不行了。分析js發(fā)現(xiàn)signature的請求是通過window.TAC.sign方法產生,而這個方法是動態(tài)綁定的,由一堆看不清邏輯的字符串通過一定的算法解密后得到,著手分析了一下,發(fā)現(xiàn)里面用到了Date ,Convas 相關的函數(shù),姑且進行了一下推斷,判斷入?yún)⒌臅r間戳跟當前的時間戳進行對比,肯定不能大于當前的時間,但是范圍也不能相差太遠否則一個頻道頁內容直接就爆了,對于Convas 可能就是為了限定上下文,否則為什么我們通過python直接執(zhí)行TAC.sign方法產生的signature在第二次就會失效,雖說第一次能請求成功那么我們是不是每次都模擬第一次請求,雖說也能得到數(shù)據(jù),但不能連續(xù)采集還是有點失望的。那有沒有其他的辦法可以解決了,在經過一下午的思考過后發(fā)現(xiàn),既然在保證上下文的情況下可以連續(xù)采集(通過chrome的console生成的signature是可以的)那么我們通過selenium方案來模擬上下文,負責產生signature,剩下的就是通過urllib來請求接口獲取數(shù)據(jù)進行解析是否可以行了?經過一番驗證結果是可喜的,功夫不負有心人達到目的。直接貼代碼。代碼中有些自有邏輯沒有貼出來,但是邏輯思路基本上都有了。今天到此為止,下次說怎么爬詳情
def get_channel_data(self, page): #獲取數(shù)據(jù)
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)) ####發(fā)布時間
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'])