引言
不知从何时起,10.24变成了程序员的节日,首先祝大家节日快乐!代码永无bug!
小编最近突然有点怀旧,想到了一个古老的网站——校内网(人人网),在小编还在读大学的那个时间,校内网真的是火的一塌糊涂,那时候的同学每天都在不停的刷校内,找同学,发布新鲜事。然而世事难料,谁也不曾想到当时那么火的校内网现在变得这么凄凉,如果不是刻意去想,可能都想不到这个网站。当想到这个网站的时候,小编突然想去访问一下这个网站,打开浏览器,输入用户名密码,还好,账号能够登陆。看着自己的好友列表,很想知道他们当时都在哪个城市。所以就写了几行代码,查询一下,也就有了今天的项目。
需求分析
爬取校内网个人主页的好友列表,然后再一次访问好友的好友列表页面,获取用户的城市信息。
需要源码的同学可以关注公众号,回复“校内网”获取源码。
知识点
爬取数据:WebDriver
多线程:ThreadPoolExecutor
图形分析:pyecharts
数据库:Mongo
获取好友列表
由于使用WebDriver操作浏览器,模拟用户登录操作,所以网页不需要进行过多的分析,只要能够定位控件位置即可。此处说一下逻辑,首先登陆校内网→访问个人主页→访问个人好友页面→提取好友信息(包括:姓名,好友主页地址,省份)→将提取的信息存入mongoDB。
从mongoDB中读取需要爬取的好友列表(用status标识)→访问好友个人主页(此处由于校内网限制,不能直接访问,需要登录自己账号后才可访问)→访问好友的好友列表→提取好友信息→将提取的信息存入mongoDB。
此处本应可以无限循环,例如我们提取到了好友的好友列表后,还可以提取好友的好友的好友列表。但是由于校内网隐私设置,我们无法访问好友的好友的好友列表页面,此处只能作罢。
之前听说过一句话,大概意思是“通过六个人,你可以认识世界上任何一个人”,本来还想着通过校内查询六次看看能够查询到多少人,结果受到校内设置影响,只能查询一次。
以下是部分代码:
登陆校内网:
def login(driver,url,username,pwd):
driver.get(url)
# 输入用户名
input_text = WebDriverWait(driver,timeout).until(
lambda d: d.find_element_by_id("email")
)
input_text.send_keys(username)
print("输入用户名 ok")
# 输入密码
password = WebDriverWait(driver,timeout).until(
lambda d: d.find_element_by_id("password")
)
password.send_keys(pwd)
print("输入密码 ok")
# 检查是否有需要输入验证码
check_code(driver)
# 点击登录按钮
login_button = WebDriverWait(driver,timeout).until(
lambda d: d.find_element_by_id("login")
)
login_button.click()
print("点击登录 ok")
# 进入个人主页
hd_name = WebDriverWait(driver,timeout).until(
lambda d: d.find_element_by_class_name("hd-name")
)
href = hd_name.get_attribute("href")
print("找到个人主页地址 ok")
return href
爬取个人主页好友列表:
def get_my_friends_info_list(driver,href):
# 访问个人主页地址
driver.get(href)
name = driver.title
print("访问%s的个人主页 ok"%name)
# 进入好友列表页面
friends_button = WebDriverWait(driver,timeout).until(
lambda d: d.find_element_by_xpath('//*[@id="specialfriend-box"]/div[1]/div/h5/a')
)
friends_button.click()
print("进入%s好友列表 ok"%name)
# 等待5s,加载数据
time.sleep(5)
# 如果滚动条不是在页面最下方,证明列表需要加载,下拉至页面最下方
len_after = 0
len_before = 1
# 如果下拉后的好友数量不等于下拉前好友的数量,就继续下拉页面
while len_after != len_before:
friends_list = WebDriverWait(driver, timeout).until(
lambda d: d.find_elements_by_class_name("friend-detail")
)
# 获取下拉之前的好友数量
len_before = len(friends_list)
# 下拉至页面最底端
js = "window.scrollTo(0,document.body.scrollHeight)"
driver.execute_script(js)
time.sleep(2)
friends_list = WebDriverWait(driver, timeout).until(
lambda d: d.find_elements_by_class_name("friend-detail")
)
# 获取下拉后的好友数量
len_after = len(friends_list)
print("滚动条向下滚动到底部一次")
# 定位 friend-detail 标签
friends_list = WebDriverWait(driver, timeout).until(
lambda d: d.find_elements_by_class_name("friend-detail")
)
print("定位li标签 ok")
# 定位 .friend-detail a 标签
personal_home_pages = WebDriverWait(driver, timeout).until(
lambda d:d.find_elements_by_css_selector(".friend-detail a")
)
print("定位a标签 ok")
# 定位省份信息
provinces_list = WebDriverWait(driver, timeout).until(
lambda d: d.find_elements_by_class_name("friends-loc-info")
)
print("定位省份信息 ok")
# 将定位的信息组装成字典并写入数据库
for i in range(len(friends_list)):
friends_info = {"data_id":friends_list[i].get_attribute("data-id"),
"name":friends_list[i].get_attribute("data-name"),
"id":friends_list[i].get_attribute("id"),
"personal_home_page":personal_home_pages[i].get_attribute("href"),
"province":provinces_list[i].get_attribute("title"),
"status":0,
"from":name,
}
print(friends_info["province"], friends_info["name"])
#将获取到的数据写入数据库
write_to_mongo(friends_info,i,name)
driver.quit()
存储数据到mongoDB:
def write_to_mongo(friends_info,i,name):
# 将数据写入数据库
curr = pymongo.MongoClient()
database = curr["renren"]
coll = database['friends']
coll.insert(friends_info)
print("%s的第%s个好友写入数据库成功"%(name,i+1))
curr.close()
数据分析:
def fenxi():
# 读取数据库中的数据
curr = pymongo.MongoClient()
database = curr["renren"]
coll = database['friends']
friends_list_all_find = coll.find({})
friends_list_all = []
for f in friends_list_all_find:
friends_list_all.append(f)
curr.close()
# 统计省份信息
result = {}
for f in friends_list_all:
if f["province"] not in result.keys():
result[f["province"]] = 1
else:
result[f["province"]] += 1
# 对统计数据进行过滤,去除没有省份的数据和小于100的数据
result_gt100 = {}
for k,v in result.items():
if v >= 100:
if k.strip():
result_gt100[k] = v
# 对数据按照省份值进行排序
result_gt100_sorted = sorted(result_gt100.items(), key=lambda x: x[1], reverse=True)
# 统计出key列表和value列表,下面生成统计图使用
key = []
value = []
for k in result_gt100_sorted:
key.append(k[0])
value.append(k[1])
# 柱形图
bar = Bar("柱状图",width=2000,height=1000)
bar.add("",key,value,is_label_show=True)
bar.render(path="柱状图.html")
# 全国地图
map = Map("全国地图示例", width=2000, height=1000)
map.add("", key, value, maptype='china',is_visualmap=True, visual_text_color="#000",is_map_symbol_show = False,
visual_range=[0, 1000])
map.render(path="地图.html")
# 词云图
wordcloud = WordCloud(width=2000, height=1000)
wordcloud.add("", key, value, word_size_range=[20, 100])
wordcloud.render(path="词云.html")
数据分析结果
本人校内网好友154位,通过这154位好友,一共抓取到了27215位好友信息(本人笔名27315,跟这个数字就差100),这里面未对好友进行去重,如果想要更精准一些,需要按照id去重。其中一位同学居然有3345个好友。。。嗯,是在下输了。
爬取到这些结果后,对数据进行了过滤,去除掉小于100的数据,再去除掉空数据(空数据是因为有些人未设置地区属性)。下面是结果分析。
地区分布柱形图:
可以看到有一个省份独占鳌头,相信同学们应该能够通过此图猜到小编是哪个省份的了。没错,就是排在第一位的黑龙江。
去除掉黑龙江之后,我们再来看一下结果:
省份还是比较平均的。
地图分布:
通过此图可以看见,好友分布由东北向西南递减。注:空白地区不是没有数据,而是数据过少被过滤掉了。
最后看下词云结果:
源码
链接:https://pan.baidu.com/s/1yXS3ryDV2PfFFKN9xdjZAg 密码:k4nz
以上内容为第一次爬取,后来在朋友的建议下做了优化:
昨天发布了爬取校内网好友分布的代码,然而还不够完善,在多线程的部分听取了一位朋友的意见,使用了submit方法,保证可以循环多次。另外将提取好友信息的部分单独拿出来做成了一个方法,方便代码以后的管理。由于是在昨日的代码中进行了优化,这次我就不写内容了,直接放上源代码。大家可以按需下载。
想要提醒大家的是,多线程如果开的太多,会导致校内网在登陆的时候使用验证码验证,目前还没有有效的自动识别验证码的方法,所以只能开少量线程,本人目前开的是3个,实测5个多线程在爬取第二轮的时候就会出现验证码,大家还是要控制爬取速度。
源码:
链接:https://pan.baidu.com/s/1RWQaR-MJbBH-CdRyIr994g 密码:kt42