- 代码、教程均为本人原创,且仅限于学习交流,请勿用于任何商业用途!
我突发奇想,想用Python爬取B站中我关注的人,我关注的人关注的人,我关注的人关注的人关注的人等。
写代码前先构思思路:既然我要爬取用户关注的用户,那我需要存储用户之间的关系,确定谁是主用户,谁是follower。
存储关系使用数据库最方便,也有利于后期的数据分析,我选择sqlite数据库,因为Python自带sqlite,sqlite在Python中使用起来也非常方便。
数据库中需要2个表,一个表存储用户的相互关注信息,另一个表存储用户的基本信息,在B站的用户体系中,一个用户的mid号是唯一的。
然后我还需要一个列表来存储所以已经爬取的用户,防止重复爬取,毕竟用户之间相互关注的现象也是存在的,列表中存用户的mid号就可以了。
最后我需要找到B站用户的关注列表的json接口,很快就找到了,地址是https://api.bilibili.com/x/relation/followings?vmid=2&pn=1&ps=20&order=desc&jsonp=jsonp&callback=__jp7
其中vimd=后的参数就是用户的mid号,pn=1指用户的关注的第一面用户,一面显示20个用户。因为B站的隐私设置,一个人只能爬取其他人的前5面关注,共100人。
先写建数据库的代码,数据库中放一个用户表,一个关系表:
def create():
# 创建数据库
global conn
conn = sqlite3.connect('data.db')
conn.execute("""
create table if not exists user(
id INTEGER PRIMARY KEY ,
mid int DEFAULT NULL,
name varchar DEFAULT NULL,
sign varchar DEFAULT NULL)""")
conn.execute("""
create table if not exists relation(
id INTEGER PRIMARY KEY ,
master int,
following int
)""")
conn.commit()
然后写爬取的核心代码:
def func(startid=0):
global user
if startid == 0:
return
i = 0
result = []
ref_url = "https://space.bilibili.com/"+str(startid)+"/#/fans/follow"
head = {
'Accept': '*/*',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'DNT': '1',
'Host': 'api.bilibili.com',
'Pragma': 'no-cache',
'Referer': ref_url,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) \
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36'
}
while 1:
i += 1
if i >= 6:
break
url = "https://api.bilibili.com/x/relation/followings?vmid=" + \
str(startid)+"&pn="+str(i) + \
"&ps=20&order=desc&jsonp=jsonp&callback=__jp5"
try:
r = requests.get(url, headers=head, timeout=10).text
r2 = eval(r[6:-1].replace('null', 'None'))
list1 = r2['data']['list']
if list1 == []:
break
else:
for user1 in list1:
result.append(
[user1["mid"], user1["uname"], user1["sign"]])
except Exception as e:
print(e)
if result != []:
save(result, startid)
在这段代码里,我使用了requests库来获取网页,其中有10行代码用于修改requests的header,防止爬虫被封。
接着写一个保存数据到数据库的函数:
def save(result=[], master=0):
# 将数据保存至本地
global conn, user
if result == [] or master == 0:
print("save error!")
return
command1 = "insert into user \
(mid,name,sign) values (?,?,?);"
command2 = "insert into relation\
(master,following)values(?,?)"
for row in result:
try:
temp = (master, row[0])
if row[0] not in user:
user.append(row[0])
conn.execute(command1, row)
conn.execute(command2, temp)
else:
conn.execute(command2, temp)
except Exception as e:
print(e)
print("insert error!")
conn.rollback()
conn.commit()
result = []
最后写main函数:
if __name__ == "__main__":
create()
cycle = 0
recordids = 0
time0 = time.time()
while 1:
cycle += 1
if recordids == 0:
users = [startid]
else:
users = user[recordids:len(user)]
for i in users:
recordids += 1
func(i)
time1 = time.time()
print("\r已爬取{0}个用户,正在第{1}层 ,总花费时间:{2:.2f}s".format(
recordids, cycle, time1-time0), end="")
main函数在运行时会提示当前花费的时间和已经爬取的用户。
我用user这个全局变量来存储所有已经爬取过的用户的mid号。
完整的代码在 bilibili-following-spider.py
我调试完程序后,已我自己作为开始的起点,花费了接近一天的时间,爬取了10万用户。
然后我想单单爬取用户的关注,然后存在数据库中是对数据是一种浪费,我打算利用我拥有的数据。
我打算利用已经爬取到本地的数据进行词云的生成,来看一下这10万用户**同的关注的哪些UP主出现的次数最多。 代码的思路主要是从数据库中获取用户的名字,重复的次数越多说明越多的用户关注,然后我使用fate的一张图片作为 词云的mask图片,最后生成词云图片。 代码如下:
import matplotlib.pyplot as plt
from wordcloud import WordCloud, ImageColorGenerator
from scipy.misc import imread
import sqlite3
conn = sqlite3.connect('data.db')
user = {}
for i in conn.execute("select mid,name from user order by id").fetchall():
user[i[0]] = i[1]
wordlist = []
for i in conn.execute("select following from relation order by id").fetchall():
if i[0] in user:
wordlist.append(user[i[0]])
wl_space_split = " ".join(wordlist)
mask_png = imread("fate.jpeg")
my_wordcloud = WordCloud(
font_path=r"C:\Windows\Fonts\simhei.ttf",# 词云自带的字体不支持中文,在windows环境下使用黑体中文
background_color="white", # 背景颜色
max_words=500, # 词云显示的最大词数
max_font_size=100, # 字体最大值
random_state=42,
mask=mask_png,
width=1000, height=860, margin=2,).generate(wl_space_split)
image_colors = ImageColorGenerator(mask_png)
plt.figure()
plt.imshow(my_wordcloud.recolor(color_func=image_colors))
plt.axis("off")
plt.figure()
plt.imshow(mask_png, cmap=plt.cm.gray)
plt.axis("off")
plt.show()
my_wordcloud.to_file("wordcloud.png")
词云生成的完整代码在bilibili-wordcloud.py
最后生成的图片为
可以看出蕾丝,暴走漫画,木鱼水心,阅后即瞎,papi酱等B站大UP主都是热门关注。
GitHub项目地址:https://github.com/zhang0peter/bilibili-following-spider