(已失效)
抖音的使用者資訊頁的網址有3種形式,分别是:
- https://v.douyin.com/GW5S6D/
- https://www.iesdouyin.com/share/user/88445518961?sec_uid=MS4wLjABAAAAWxLpO0Q437qGFpnEKBIIaU5-xOj2yAhH3MNJi-AUY04×tamp=1582709424&utm_source=copy&utm_campaign=client_share&utm_medium=android&share_app_name=douyin
- https://www.douyin.com/share/user/88445518961
連結1是從用戶端分享的短連結,在浏覽器位址欄輸入後重定向至連結2的形式。連結2和連結3很明顯地把使用者的UID顯示出來,這個使用者的UID為88445518961。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL0kzM1UTMyQTM3IjMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
目标:抓取使用者名、儲存使用者頭像、UID、抖音ID、簽名、關注數、粉絲數、贊數、作品數、喜歡數
0. 擷取HTML
使用requests獲得響應并利用BeautifulSoup來查找目标值,代碼如下
import requests
from bs4 import BeautifulSoup as bs
def getHtml(url):
header = {"user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60"}
#try: 不必處理異常
response = requests.get(url=url, headers=header)
return bs(response.text, 'lxml') # lxml需要安裝,可以選用html.parser
#except: 不必處理異常
# print('網址錯誤')
# quit()
url = 'https://www.iesdouyin.com/share/user/88445518961?sec_uid=MS4wLjABAAAAWxLpO0Q437qGFpnEKBIIaU5-xOj2yAhH3MNJi-AUY04×tamp=1582709424&utm_source=copy&utm_campaign=client_share&utm_medium=android&share_app_name=douyin'
soup = getHtml(url)
1. 抓取使用者名
隻要查找class="nickname"屬性的标簽<p>,标簽内的内容即使用者名
nickname = soup.find(name='p', attrs={'class': 'nickname'}).string
2. 儲存頭像
紅色框内src的屬性值就是頭像的下載下傳連結。同樣通過find函數查找class="avatar"的标簽<img>裡另一屬性src的值,下載下傳圖像後,檔案儲存在avatar目錄下,檔案名為nickname.jpeg。定義一個儲存頭像的函數
import os
def getAvatar(soup, nickname):
avatarAddr = soup.find(name='img', attrs={'class': 'avatar'})['src']
avatar = requests.get(avatarAddr)
if not os.path.exists('avatar'): # 判斷目錄是否存在,如果不存在,則建立
os.makedirs('avatar')
try:
with open('.\\avatar\\'+nickname+'.jpeg', 'wb') as img:
img.write(avatar.content)
print('頭像儲存成功 avatar\\'+nickname+'.jpeg')
except:
print('頭像儲存失敗')
tag[attr]傳回的是tag标簽内屬性為attr的值
3. UID
UID的資料在“關注”按鈕位置
uid = soup.find(name='span', attrs={'class': 'focus-btn go-author'})['data-id']
4. 抖音ID
右邊紅色框内是使用者的抖音id。抖音對數字進行一些處理,這是一種反爬蟲的應對措施,使我們不能輕易獲得資料。每個黑色方框代表一個數字,其實這些數字由特殊的字型控制着,一組Unicode碼對應一個數字。
依次選中Network、Font,重新整理網頁,iconfont_9eb9a50.woff字型檔案出現了。選中iconfont_9eb9a50.woff,出現下圖的下載下傳連結。把檔案下載下傳下來分析數字和Unicode碼的映射關系
網上提到百度字型編輯器是一個非常好用的線上編輯器。這個例子裡使用font creator檢視
每一個數字由3個十六進制代碼編碼,具體檢視網頁源碼的時候發現,每一次加載網頁一個數字隻由四位十六進制代碼指定,如下圖
是以隻需要找出所有的數字的映射關系就可以輕松地解碼數字,利用詞典能友善地實作。
codeMap = {
'\ue603': '0', '\ue60d': '0', '\ue616': '0',
'\ue602': '1', '\ue60E': '1', '\ue618': '1',
'\ue605': '2', '\ue610': '2', '\ue617': '2',
'\ue604': '3', '\ue611': '3', '\ue61a': '3',
'\ue606': '4', '\ue60c': '4', '\ue619': '4',
'\ue607': '5', '\ue60f': '5', '\ue61b': '5',
'\ue608': '6', '\ue612': '6', '\ue61f': '6',
'\ue60a': '7', '\ue613': '7', '\ue61c': '7',
'\ue60b': '8', '\ue614': '8', '\ue61d': '8',
'\ue609': '9', '\ue615': '9', '\ue61e': '9'
}
定義一個函數實作把經過“加密”的字元串轉化成普通的字元串
def code2commStr(code):
retStr = ''
for c in code:
if c in codeMap:
retStr += codeMap[c]
else:
retStr += c
return retStr
再定義一個函數獲得含有這類數字的資訊
def getInfo(soup, tag, attr):
element = soup.find(name=tag, attrs={'class': attr})
return code2commStr(element.text.split())
find函數的傳回對象的string和text屬性有所不同。當find傳回的标簽含有子标簽時,string屬性的值為None,text屬性的值為純文字(包含空格)。把Unicode純文字以空格為間隔分割每一個字元,使用code2commStr函數轉化。為了隻提取ID而不要其他文字,把shortid以中文冒号(“:”)為分割,隻取後半部分。
shortid = getInfo(soup, 'p', 'shortid').split(':')[-1]
5. 簽名
因為簽名中的數字沒有經過處理,是以直接獲得即可。
signature = soup.find(name='p', attrs={'class': 'signature'}).string
6. 關注數、粉絲數、贊數、作品數、喜歡數
這5個資料都含有經過處理的數字,和獲得抖音ID的方法相同,利用getInfo函數提取
focus = getInfo(soup, tag='span', attr='focus block')[:-2] # 關注
follower = getInfo(soup, tag='span', attr='follower block')[:-2] # 粉絲
likedNum = getInfo(soup, tag='span', attr='liked-num block')[:-1] # 贊
work = getInfo(soup, tag='div', attr='user-tab active tab get-list')[2:] # 作品
like = getInfo(soup, tag='div', attr='like-tab tab get-list')[2:] # 喜歡
為了提取數字本身,丢掉前(後)幾個字元(“關注”、“粉絲”、“贊”、“作品”、“喜歡”)。
至此,利用Python爬取抖音分享頁使用者資訊的代碼基本完成,但是在測試過程中發現一個錯誤。
出現這個錯誤的位置是簽名,原因是一些使用者的個性簽名很有個性,帶有特殊符号,Tk不支援顯示這種符号。
一個不完美的方法是不管該符号,保留其他符号顯示。 将使用者名和簽名等可能含有特殊符号的資訊進行修改
import sys
nonBmpMap = dict.fromkeys(range(0x10000, sys.maxunicode + 1), 0xfffd)
nickname = soup.find(name='p', attrs={'class': 'nickname'}).string.translate(nonBmpMap)
signature = soup.find(name='p', attrs={'class': 'signature'}).string.translate(nonBmpMap)
完整代碼如下:
import requests
from bs4 import BeautifulSoup as bs
import os
import sys
codeMap = {
'\ue603': '0', '\ue60d': '0', '\ue616': '0',
'\ue602': '1', '\ue60E': '1', '\ue618': '1',
'\ue605': '2', '\ue610': '2', '\ue617': '2',
'\ue604': '3', '\ue611': '3', '\ue61a': '3',
'\ue606': '4', '\ue60c': '4', '\ue619': '4',
'\ue607': '5', '\ue60f': '5', '\ue61b': '5',
'\ue608': '6', '\ue612': '6', '\ue61f': '6',
'\ue60a': '7', '\ue613': '7', '\ue61c': '7',
'\ue60b': '8', '\ue614': '8', '\ue61d': '8',
'\ue609': '9', '\ue615': '9', '\ue61e': '9'
}
def code2commStr(code):
retStr = ''
for c in code:
if c in codeMap:
retStr += codeMap[c]
else:
retStr += c
return retStr
def getHtml(url):
header = {"user-agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36 OPR/26.0.1656.60"}
#try: 不必處理異常
response = requests.get(url=url, headers=header)
return bs(response.text, 'lxml') # lxml需要安裝,可以選用html.parser
#except:
# print('網址錯誤')
# quit()
def getAvatar(soup, nickname):
avatarAddr = soup.find(name='img', attrs={'class': 'avatar'})['src']
avatar = requests.get(avatarAddr)
if not os.path.exists('avatar'):
os.makedirs('avatar')
try:
with open('.\\avatar\\'+nickname+'.jpeg', 'wb') as img:
img.write(avatar.content)
print('頭像儲存成功 avatar\\'+nickname+'.jpeg')
except:
print('儲存頭像失敗')
def getInfo(soup, tag, attr):
element = soup.find(name=tag, attrs={'class': attr})
return code2commStr(element.text.split())
url = 'https://www.iesdouyin.com/share/user/88445518961?sec_uid=MS4wLjABAAAAWxLpO0Q437qGFpnEKBIIaU5-xOj2yAhH3MNJi-AUY04×tamp=1582709424&utm_source=copy&utm_campaign=client_share&utm_medium=android&share_app_name=douyin'
soup = getHtml(url)
nonBmpMap = dict.fromkeys(range(0x10000, sys.maxunicode + 1), 0xfffd)
nickname = soup.find(name='p', attrs={'class': 'nickname'}).string.translate(nonBmpMap)
getAvatar(soup, nickname)
shortid = getInfo(soup, 'p', 'shortid')
uid = soup.find(name='span', attrs={'class': 'focus-btn go-author'})['data-id']
signature = soup.find(name='p', attrs={'class': 'signature'}).string.translate(nonBmpMap)
focus = getInfo(soup, tag='span', attr='focus block')[:-2]
follower = getInfo(soup, tag='span', attr='follower block')[:-2]
likedNum = getInfo(soup, tag='span', attr='liked-num block')[:-1]
work = getInfo(soup, tag='div', attr='user-tab active tab get-list')[2:]
like = getInfo(soup, tag='div', attr='like-tab tab get-list')[2:]
print('使用者名:', nickname)
print('抖音ID:', shortid)
print('UID:', uid)
print('簽名:', signature)
print('關注數:', focus)
print('粉絲數:', follower)
print('贊數:', likedNum)
print('作品:', work)
print('喜歡:', like)
結果
不過,這個程式意義不大,除非可以高效獲得大量使用者UID,或者隻是為了提取幾個使用者的資料變化。