本文是我第三篇爬虫实战的代码,主要针对拉勾这一类有反爬机制的网站构思爬取思路,并对爬取的数据进行可视化及分析
本来信心满满觉得可以写出这篇代码,但自己第一次完成的时候并不能成功爬取,于是我在各个网站上学习了各路大佬的思路,很有启发,下面的代码是我模仿其中最喜欢的爬虫代码风格写出的
第一部分是爬虫的代码
import requests
import pandas
import time
import random
#用于获取页面信息
def getWebResult(url,cookies,form,header):
html = requests.post(url=url,cookies=cookies,data=form, headers=header)
result = html.json()
#找到html中result包含的招聘职位信息
data = result['content']['positionResult']['result'] # 返回结果在preview中的具体返回值
return data
# if 'content' in result:
# data = result['content']['positionResult']['result'] # 返回结果在preview中的具体返回值
# return data
# else:
# print('not exist')
# return None
### 在文末会提到以上注释的含义
#将招聘信息按照对应的参数,组装成字典
def getGoalData(data):
for i in range(15):#每页默认15个职位
info={
'positionName': data[i]['positionName'], #职位简称
'companyShortName': data[i]['companyShortName'], #平台简称
'salary': data[i]['salary'], #职位薪水
'createTime': data[i]['createTime'], #发布时间
'companyId':data[i]['companyId'], #公司ID
'companyFullName':data[i]['companyFullName'], #公司全称
'companySize': data[i]['companySize'], #公司规模
'financeStage': data[i]['financeStage'], #融资情况
'industryField': data[i]['industryField'], #所在行业
'education': data[i]['education'], #教育背景
'district': data[i]['district'], #公司所在区域
'businessZones':data[i]['businessZones'] #区域详细地
}
data[i]=info
return data
#保存data至csv文件
def saveData(data,stage):
table = pandas.DataFrame(data)
table.to_csv('lagou.csv', header=stage, index=False, mode='a+')
def main():
# 拼装header信息
header = {
'Host': 'www.lagou.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Referer': 'https://www.lagou.com/jobs/list_%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90?px=default&city=%E6%88%90%E9%83%BD',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'X-Anit-Forge-Token': 'None',
'X-Anit-Forge-Code': '0',
'Content-Length': '55',
'Connection': 'keep-alive',
'Pragma': 'no-cache',
'Cache-Control': 'no-cache, no-store, max-age=0'
}
cookies = {
'Cookie':'user_trace_token=20190701195626-413b5187-9bf7-11e9-bb89-525400f775ce; LGUID=20190701195626-413b53d1-9bf7-11e9-bb89-525400f775ce; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%2216bad64b2e0177-0b9dbc99c10c49-e343166-1327104-16bad64b2e16f2%22%2C%22%24device_id%22%3A%2216bad64b2e0177-0b9dbc99c10c49-e343166-1327104-16bad64b2e16f2%22%2C%22props%22%3A%7B%22%24latest_traffic_source_type%22%3A%22%E7%9B%B4%E6%8E%A5%E6%B5%81%E9%87%8F%22%2C%22%24latest_referrer%22%3A%22%22%2C%22%24latest_referrer_host%22%3A%22%22%2C%22%24latest_search_keyword%22%3A%22%E6%9C%AA%E5%8F%96%E5%88%B0%E5%80%BC_%E7%9B%B4%E6%8E%A5%E6%89%93%E5%BC%80%22%7D%7D; _ga=GA1.2.801986302.1561982196; JSESSIONID=ABAAABAAADEAAFI4839E11CD94A977EEB2B1E95652FD697; WEBTJ-ID=20190810164200-16c7ab1117275-04bee1efa35a37-c343162-1327104-16c7ab111735a3; index_location_city=%E5%85%A8%E5%9B%BD; isCloseNotice=0; TG-TRACK-CODE=search_code; X_MIDDLE_TOKEN=a17a1fb36651540303bd945a47e31611; X_HTTP_TOKEN=c8d3763e5add9d9d9787255651a16cc9be556fcc48; LGRID=20190811205119-b72d08e6-bc36-11e9-8915-525400f775ce; SEARCH_ID=84e789e6c95a450bacac50045bccfaca'
}
# 职位关键字
job='数据分析'
# 职位所属地
city = '成都'
# 模拟请求的url
url = 'https://www.lagou.com/jobs/positionAjax.json?px=default&city=' + city + '&needAddtionalResult=false'
#用于定义开始爬取的起始页码
startPage=1
#拉勾网有个限制,单次只能连续爬取5页,所以使用一个以5页为轮循的小策略
while startPage<26:
for i in range(startPage, startPage+5):
#拼装Form Data信息
if i == 1:
flag = 'true' #当是首次请求时,使用flag=true标志
stage = True #stage是用来标示csv是否创建表头的参数,仅在第一次保存数据时创建
else:
flag = 'false'
stage = False
num = i
form = {'first': flag, # 标示是否是首次请求标示,第二页以后则为false
'kd': job,
'pn': str(num)}
print('------page %s-------' % i) #打印当面爬取的页码
#调用函数,获取相应的招聘信息
data = getWebResult(url,cookies,form, header)
#调用函数,拼装招聘信息
data_goal = getGoalData(data)
#调用函数,保存info数据
saveData(data_goal, stage)
#以5页为单次,依次轮循
startPage+=5
#休眠一定时间
time.sleep(20+random.randint(10,30))
if __name__ == '__main__':
main()
这里解答一下问题,这个代码无法运行成功,始终会报'content'错误.
我并不能理解为什么,且发现网上其他作者的不同代码我运行都会报这个错,我搜索了相关解决方案,只找到如下:
我尝试了这个方法(即大概为上述代码的注解),但是报出另一个错误:我定义的(如图)字典键不存在值.我完全不能理解了...尝试许久还是没有解决,但这部分的代码和思路倒是十分熟练,只好告诉自己有缘再解决这个问题了...
下面是分析相关的代码
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from wordcloud import WordCloud
from scipy.misc import imread
import jieba
from pylab import mpl
# 使matplotlib模块能显示中文
mpl.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
mpl.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题
# 读取数据
df = pd.read_csv('lagou_jobs.csv', encoding = 'gbk')
# 数据清洗,剔除实习岗位
df.drop(df[df['职位名称'].str.contains('实习')].index, inplace=True)
# print(df.describe())
# 由于CSV文件内的数据是字符串形式,先用正则表达式将字符串转化为列表,再取区间的均值
pattern = '\d+'
df['工作年限'] = df['工作经验'].str.findall(pattern)
avg_work_year = []
for i in df['工作年限']:
# 如果工作经验为'不限'或'应届毕业生',那么匹配值为空,工作年限为0
if len(i) == 0:
avg_work_year.append(0)
# 如果匹配值为一个数值,那么返回该数值
elif len(i) == 1:
avg_work_year.append(int(''.join(i)))
# 如果匹配值为一个区间,那么取平均值
else:
num_list = [int(j) for j in i]
avg_year = sum(num_list)/2
avg_work_year.append(avg_year)
df['经验'] = avg_work_year
# 将字符串转化为列表,再取区间的前25%,比较贴近现实
df['salary'] = df['工资'].str.findall(pattern)
avg_salary = []
for k in df['salary']:
int_list = [int(n) for n in k]
avg_wage = int_list[0]+(int_list[1]-int_list[0])/4
avg_salary.append(avg_wage)
df['月工资'] = avg_salary
# 将清洗后的数据保存,以便检查
df.to_csv('draft.csv', index = False)
# 描述统计
print('数据分析师工资描述:\n{}'.format(df['月工资'].describe()))
# 绘制频率直方图并保存
plt.hist(df['月工资'],bins = 12)
plt.xlabel('工资 (千元)')
plt.ylabel('频数')
plt.title("工资直方图")
plt.savefig('histogram.jpg')
plt.show()
# 绘制饼图并保存
count = df['区域'].value_counts()
# 将龙华区和龙华新区的数据汇总
count['龙华新区'] += count['龙华区']
del count['龙华区']
plt.pie(count, labels = count.keys(),labeldistance=1.4,autopct='%2.1f%%')
plt.axis('equal') # 使饼图为正圆形
plt.legend(loc='upper left', bbox_to_anchor=(-0.1, 1))
plt.savefig('pie_chart.jpg')
plt.show()
# 绘制词云,将职位福利中的字符串汇总
text = ''
for line in df['职位福利']:
text += line
# 使用jieba模块将字符串分割为单词列表
cut_text = ' '.join(jieba.cut(text))
color_mask = imread('cloud.jpg') #设置背景图
cloud = WordCloud(
font_path = 'yahei.ttf',
background_color = 'white',
mask = color_mask,
max_words = 1000,
max_font_size = 100
)
word_cloud = cloud.generate(cut_text)
# 保存词云图片
word_cloud.to_file('word_cloud.jpg')
plt.imshow(word_cloud)
plt.axis('off')
plt.show()
# 实证统计,将学历不限的职位要求认定为最低学历:大专
df['学历要求'] = df['学历要求'].replace('不限','大专')
# 学历分为大专\本科\硕士,将它们设定为虚拟变量
dummy_edu = pd.get_dummies(df['学历要求'],prefix = '学历')
# 构建回归数组
df_with_dummy = pd.concat([df['月工资'],df['经验'],dummy_edu],axis = 1)
# 建立多元回归模型
y = df_with_dummy['月工资']
X = df_with_dummy[['经验','学历_大专','学历_本科','学历_硕士']]
X=sm.add_constant(X)
model = sm.OLS(y,X)
results = model.fit()
print('回归方程的参数:\n{}\n'.format(results.params))
print('回归结果:\n{}'.format(results.summary()))
(因为爬取部分始终没能成功,所以分析部分的代码也就没能实施辽 hin难受)
总结
1.找到了自己爬虫的代码风格,也对这一类有反爬机制的网站爬取有了一系列心得体会;
2.对使用 python 进行数据分析有了实践和更多的思考