灵感来源与学习:《利用 Python 爬取了 13966 条运维招聘信息,我得出了哪些结论?》
本文原创作者:壹加柒
前几天手机上CSDN推荐了一篇文章《利用 Python 爬取了 13966 条运维招聘信息,我得出了哪些结论?》,恰好最近也在学习爬虫相关知识,打开一看,从数据的爬取——》数据的清洗——》数据的可视化,涵盖的知识是很多我从前没有尝试过的。之前一直的实战一直停留在数据的爬取,没有对数据进行分析使得数据在我手上似乎失去了价值。
于是我想着跟着这篇文章的思路,爬取个有意思的网站。本来想着爬取学校的数据,但学校也没啥好爬的,而且稍不注意爬取到私密数据,也有可能会凉凉,然后送上一副银手镯。现在的单身(*多的吧,哈哈,那就爬取婚恋网站的数据,分析样本来祝你脱单一臂之力!
说干就干,没想到一干就花了整整三天,如果觉得文章对你有帮助,那就点个赞吧。下面正式开始。
我在分析了 世纪佳缘、有缘网、百合网后发现,出百合网外,其余两个网站非会员限制查看匹配求偶信息数,一般只有10多条数据,不充钱,你依旧是那单身的少年。可能有些接口会没进行处理,大家可自行摸索(我在分析百合网的时候发现了一些有意思的接口)。于是我为了方便决定爬取百合网。
爬取目标 | 百合网 |
---|---|
网站地址 | https://www.baihe.com/ |
样本大小 | 2875条 |
爬取对象 | 单身女性 |
分析数据 | 年龄、身高、地区、择偶要求等 |
在爬取数据这一块整整花了一天多的时间,遇到了很多问题,比如相应到的非JSON格式数据、分析了很多接口等等。有些细节忘记了,因为实战比较少,所以对于有些反爬机制没有点头绪。
分析了搜索页,这里默认了地区和年龄作为搜索条件。在下拉时候数据是动态加载的,抓包发现动态加载的数据是通过发送Post请求。
很有意思的是Post的Data域中携带了两个参数:userIDs、jsonCallBack
userIDs:包含了8个用户的ID,查询用户信息时可以通过添加用户ID。
jsonCallBack:这个参数很诡异(如:jQuery18303820131843585586_1594609772504),后面的可以看出是时间戳,前面的是啥?这个参数重不重要?
经过翻阅资料以及无携带参数访问,发现这个参数是重要的:
https://www.runoob.com/json/json-jsonp.html
https://www.cnblogs.com/xmaomao/p/3360989.html
在上一个动态加载数据时发送请求的参数很奇怪,这些参数是哪来的呢?
这边有个page的参数,到时候换页需要用上
上边我把dataType也圈了出来,因为相应的参数非正常JSON格式,我在这里花了很久,也发现了一些蛮好玩的接口。
我把响应的数据进行了格式化,最终通过正则,又变成了我熟悉的JSON字符串~ 这样提取也就方便了
url_tails = session.post(url=url_base, headers=HEADERS, data=data).content.decode('utf-8')
json_data = re.search(r'jQuery1830923921797491073_1594465799055\((.*)\);', url_tails, flags=0)
URL分析:
https://profile1.baihe.com/?oppID=229672724
由此可见,个人信息页数据结构是 https://profile1.baihe.com/?oppID= + 上边的解析出的UID
通过循环遍历每个UID提取到个人信息页我们所需要的的数据
个人信息页面分析
分析了下这几个参数,发现第一个是女生的信息,第二个是理想伴偶的标准
这样大致知道了页面结构,接下来给爷爬!
# 需要获得的数据,通过xpath解析
# 女生年龄
me_age.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[2]/dd[1]/text()')[0])
# 女生身高
me_height.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[3]/dd[1]/text()')[0])
# 女生教育
me_education.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[4]/dd[1]/text()')[0])
# 女生薪水
me_salary.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[5]/dd[1]/text()')[0])
# 女生家乡
me_location.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[8]/dd[1]/text()')[0])
# 女生婚姻
me_marriage.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[6]/dd[1]/text()')[0])
# 女生购房
me_home.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[7]/dd[1]/text()')[0])
# 女生介绍
me_introduce.append(tree.xpath('//*[@id="profileCommon"]/div[1]/div[2]/div[1]/text()')[0])
# 择偶年龄
he_age.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[2]/dd[3]/text()')[0])
# 择偶身高
he_height.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[3]/dd[3]/text()')[0])
# 择偶教育
he_education.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[4]/dd[3]/text()')[0])
# 择偶薪水
he_salary.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[5]/dd[3]/text()')[0])
# 择偶家乡
he_location.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[8]/dd[3]/text()')[0])
# 择偶婚姻
he_marriage.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[6]/dd[3]/text()')[0])
# 择偶购房
he_home.append(tree.xpath('//*[@id="matching_detail"]/div/div/dl[7]/dd[3]/text()')[0])
因为部分数据涉及隐私,所以我没有对对应的UID进行爬取。
这边没有进行模拟登陆,而是直接携带Cookie
本来想爬取个至少1万条数据,后来因为一个异常,中断在了不到3000条,时间关系,我没有继续处理
这边还是有很多需要处理的数据,我就展示一部分吧
import pandas as pd
import numpy as np
import re
import jieba
df = pd.read_csv("sample.csv",encoding="gbk",header=None)
df.head()
# 指定行索引
df.index = range(len(df))
# 指定列索引
df.columns = ['年龄', '身高', '学历', '工资', '家乡', '婚姻', '住房', '自我介绍', '对象年龄', '对象身高', '对象学历', '对象薪水', '对象家乡', '对象婚姻', '对象住房']
df.head()
df.isnull().any(axis = 0)
print('去重前数据量:', df.shape)
# 去重
df.drop_duplicates(inplace=True)
print('去重后数据量:', df.shape)
df['年龄'] = df['年龄'].str[0:2]
df.head()
# 对工资进行处理
def get_salary_max_min(salary):
try:
result = re.split('-', salary)
return result
except:
return salary
salary = df['工资'].apply(get_salary_max_min)
df['最低工资'] = salary.str[0]
df['最高工资'] = salary.str[1]
indexs = df[df['最低工资'] == '2000以下'].index
df.loc[indexs, '最低工资'] = '2000'
df.loc[indexs, '最高工资'] = '2000'
df.head()
df['最高工资'] = pd.to_numeric(df['最高工资'])
df['最低工资'] = pd.to_numeric(df['最低工资'])
df.info()
df['平均工资'] = df[['最低工资', '最高工资']].mean(axis=1)
feature = ['年龄', '身高', '学历', '工资', '家乡', '婚姻', '住房', '自我介绍', '对象年龄', '对象身高',
'对象学历', '对象薪水', '对象家乡', '对象婚姻', '对象住房', '最低工资', '最高工资', '平均工资',
'对象最低年龄', '对象最高年龄', '对象平均年龄', '对象最低身高', '对象最高身高', '对象平均身高', '对象最低薪水',
'对象最高薪水', '对象平均薪水']
final_df = df[feature]
final_df.to_excel(r"可视化.xlsx",encoding="gbk",index=None)
数据可视化这一部分我是最陌生的,所以很多样式都和杰哥(开头提到CSDN推荐文章的作者)类似的,学着学着对echarts有了些了解,认识到了pyecharts更是非常强大。
这里我将放出部分数据可视化源码。
import pandas as pd
df = pd.read_excel("可视化.xlsx",encoding="gbk")
df.head()
import pyecharts.options as opts
from pyecharts import options
from pyecharts.charts import Bar
name = sort_age.index.tolist()
value = sort_age.values.tolist()
bar3 = (
Bar(init_opts=opts.InitOpts(width='1000px', height='420px')).add_xaxis(xaxis_data=name)
.add_yaxis(series_name='18-37岁单身女性数量分析', y_axis=value)
.set_global_opts(title_opts=opts.TitleOpts(title="可切换查看曲线图"),
legend_opts=opts.LegendOpts(is_show=True))
)
bar3.set_global_opts(toolbox_opts=opts.ToolboxOpts(is_show=True))
bar3.render_notebook()
似乎明白了点什么?
import pyecharts.options as opts
from pyecharts import options
from pyecharts.charts import Line
line_man1 = (
Bar(init_opts=opts.InitOpts(width='750px', height='350px'))
.add_xaxis(xaxis_data=name)
.add_yaxis(series_name='对象男性身高均值Top10(样本整体均值:178cm)', y_axis=value)
# 下面两行代码,用于旋转坐标轴
.reversal_axis()
.set_series_opts(label_opts=opts.LabelOpts(position="right"))
)
# line_man1.set_global_opts(toolbox_opts=opts.ToolboxOpts(is_show=True))
line_man1.render_notebook()
我哭了,我连平均值都没达到,呜呜呜
from pyecharts.charts import Pie
import pyecharts.options as opts
num = avg_salary.values.tolist()
lab = avg_salary.index.tolist()
x = [(i, j)for i, j in zip(lab, num)]
pie = (Pie(init_opts=opts.InitOpts(width='750px', height='350px'))
.add(series_name='目标对象男性平均工资粗略分布(样本均值:12437)',data_pair=[(i, j)for i, j in zip(lab, num)],radius = ['40%','75%'])
.set_global_opts(title_opts=opts.TitleOpts(title="全样本均值:12437元"),
legend_opts=opts.LegendOpts(is_show=True))
)
pie.render_notebook()
平均值月薪12437元,小伙伴你达到平均值了吗?
这个词云不是很准,很多语句都是官方默认的,大家看看就好。
还是挺感谢杰哥以及CSDN的,有着他们的指引,我才完成了此次爬取+数据分析的整个过程。
学习到了蛮多的,在三天时间里,更进一步理解了些反爬机制,对于数据清洗也能进一步运用在实战上,对于pycharts,一个全新的知识,也是GET到了不少。
大家也可以在我自己的网站下看这些数据:
翻身的咸鱼:http://fishei.cn/partner.html
我把全部的源码开放在了Github上,里面有这详细的注释,如果对你学习有帮助,记得点赞 + star~
https://github.com/ujiaqi/crawler-baihe
crawler-baihe
如果遇到疑问可以留言,喜欢与Learner交流。
By:壹加柒