Python高级应用程序设计任务

一、主题式网络爬虫设计方案(15分)

1.主题式网络爬虫名称

基于智联招聘全国python岗位数据爬虫

2.主题式网络爬虫爬取的内容与数据特征分析

  2.1爬取的内容

    抓取来源,岗位名称,薪资,地址,工作经验,学历要求,公司名称,公司行业,公司类型,公司规模,公司福利

  2.2数据特征分析

  分析python岗位地区热度,公司共有福利,薪资比例,学历要求比例

3.主题式网络爬虫设计方案概述(包括实现思路与技术难点)

  实现思路:

  1、通过网页请求查看网页格式以及请求过程

     2、使用requests库爬取数据

  3、通过xpath语法获取所要的相关数据

  4、使用pyecharts1.2版本进行可视化

  技术难点:

  城市信息隐藏在网页中,需要仔细查看才知道,并使用正则匹配出来。页面数据需要去找对应请求的接口

二、主题页面的结构特征分析(15分)

  1、主题页面的结构特征

  通过浏览器的F12键打开窗口,然后切换到NetWork下,选择第一个,然后点response,找到城市数据,页面岗位信息并没有在html中查看到,发现是通过异步加载的,通过xhr筛选查看到请求接口

  2、HTML页面解析

  1. 城市信息隐藏在网页中

      

 

 

     然后每个城市下的页数不定,需要动态判断页数然后抓取

    

      Python高级应用程序设计任务_第1张图片

 

 

       

    数据全是异步加载,可以通过network下筛选XHR方式查看到

       Python高级应用程序设计任务_第2张图片

 

 

       Python高级应用程序设计任务_第3张图片

   3、 节点(标签)查找方法与遍历发法(必要时画出节点数结构)

  通过正则匹配出城市信息,然后通过正则解析遍历每个城市,然后通过请求json数据,解析每个岗位的信息,而页数,则是通过返回json中的numTotal 来判定,因为每页数据量是90条,而页数就是条数除以90,如果有余数就加1页

 

三、网络爬虫程序设计(60分)

  爬虫程序主体要包括以下各部分,要附源代码及较详细注释,并在每部分程序后面提供输出结果的截图。

  1、数据爬取与采集

  爬取网页中城市信息  

 1 # 构建待抓取url列表
 2 def get_province():
 3     # 随意访问一个网址
 4     start_url = 'https://sou.zhaopin.com/?p=12&jl=489&sf=0&st=0&kw=%E8%BD%AF%E4%BB%B6&kt=3'
 5     # 发起请求
 6     rsp = requests.get(url=start_url, headers=headers)
 7     # 正则匹配城市信息
 8     city_str = re.search('__INITIAL_STATE__=(.*?)', rsp.text, re.S).group(1)
 9     # 解析成json格式
10     city_json = json.loads(city_str)
11     province = city_json['basic']['dict']['location']['province']
12     return province

  循环遍历城市,抓取每个城市下的信息 

1 # 存放抓取结果
2 page_data = []
3 for item in province:
4     for sub in item['sublist']:
5         # 启动爬虫
6         result = start_zl(item['name'], sub['code'], keyword)
7         if len(result) != 0:
8             page_data.extend(result)

  每个城市下的抓取代码,以及获取页数翻页

 1 def start_zl(city_name, city_code, keyword):
 2     print("抓取城市:" + city_name + "_" + city_code + "信息中")
 3     page = 0
 4     max = None
 5     result = []
 6     while True:
 7         print('当前{}页中'.format(page + 1))
 8         next_page = page * 90
 9         now_time = int(time.time() * 1000)
10         url = "https://fe-api.zhaopin.com/c/i/sou?" \
11               "pageSize=90" \
12               "&cityId={}" \
13               "&salary=0,0&workExperience=-1&education=-1&companyType=-1&employmentType=-1&jobWelfareTag=-1&" \
14               "kw={}" \
15               "&start={}" \
16               "&kt=3&_v={}" \
17               "&x-zp-page-request-id=84cec3861cb34e77a0f76870970d07c1-{}-988542" \
18               "&x-zp-client-id=cd61dab4-7d6d-4c58-a963-b9a4c1474d9f".format(city_code, keyword, next_page,
19                                                                             random.random(), now_time)
20         while True:
21             try:
22                 rsp = requests.get(url=url, headers=headers)
23                 desc_json = json.loads(rsp.text)
24                 if desc_json['data']['numTotal'] == 0:
25                     max = 0
26                     break
27                 # 获取页数
28                 if max is None:
29                     count = desc_json['data']['numTotal']
30                     if count <= 90:
31                         max = 1
32                     else:
33                         if count % 90 == 0:
34                             max = count // 90
35                         else:
36                             max = count // 90 + 1
37                 for item in desc_json['data']['results']:
38                     try:
39                         result.append(
40                             ['zhilian', item['jobName'], item['salary'], city_name, item['workingExp']['name'], item['eduLevel']['name'], item['company']['name'],
41                              item['jobType']['items'][0]['name'], item['company']['type']['name'],
42                              item['company']['size']['name'], '_'.join(item['welfare'])])
43                     except Exception as e:
44                         continue
45                 break
46             except Exception as e:
47                 print(url)
48                 print("错误:", e, ", 返回结果:", rsp.text)
49         if page >= max:
50             break
51         page += 1
52 
53     return result

  2、对数据进行清洗和处理

  通过pandas读取csv文件

1 # 读取excel文件
2 df = pd.read_csv('最终数据.csv', encoding='utf-8')

  对学历信息进行处理,合并中专、中技到中专/中技,将不明数据合并到其他中

 1 # 格式化学历信息
 2 education = []
 3 for c in df['学历要求']:
 4     if '' in c:
 5         c = '其他'
 6     elif c == '中专' or c == '中技':
 7         c = '中专/中技'
 8     education.append(c)
 9 df['education'] = education
10 # 按楼层分组
11 df_education = df.groupby(df['education'])
12 df_education = df_education.size()
13 value = df_education.values
14 attr = list(df_education.keys())

     对薪资数据进行处理,首先去掉面议等数据,然后针对薪资,抓取格式是2-10k这种,我们正则匹配出数字,两位的就取两位数字和,然后除2,即中位值

 1 pattern = '\d+'
 2 # 去除 "薪资面议" 等无效数据
 3 df.drop(df[df['薪资'].str.contains('面议')].index, inplace=True)
 4 
 5 salary_list = df['薪资'].values
 6 # 将字符串转化为列表
 7 df['salary'] = df['薪资'].str.findall(pattern)
 8 avg_salary = []
 9 # 统计平均工资
10 for salary in salary_list:
11     k = re.findall(pattern, salary)
12     int_list = [int(n) for n in k]
13     # 取工资区间平均值
14     if len(int_list) == 2:
15         avg_wage = (int_list[1] + int_list[0]) / 2
16     else:
17         avg_wage = int_list[0]
18     # 去除无效数据
19     if 0 < avg_wage < 100:
20         avg_salary.append(avg_wage * 1000)

  3、文本分析

  针对公司福利采用了jieba分词,并进行词云图展示

    Python高级应用程序设计任务_第4张图片

 1 # 公司福利词云图
 2 def word_cloud():
 3     wc = WordCloud({"theme": ThemeType.MACARONS})
 4     word_dict = {}
 5     for word in df['公司福利']:
 6         if 'nan' in str(word):
 7             continue
 8         r = r"[0-9\s+\.\!\/_,$%^*()?;;:-【】+\"\']+|[+——!,;:。?、~@#¥%……&*()]+"
 9         # 去除特殊符号
10         word = re.sub(r, '', str(word), re.S)
11         # cut分词,并去重
12         words = set(jieba.cut(word, cut_all=False))
13         # 统计词频
14         for word in words:
15             if len(word) < 2:
16                 continue
17             if word not in word_dict:
18                 word_dict[word] = 1
19             else:
20                 word_dict[word] += 1
21     # 词频排序
22     word_list = sorted(word_dict.items(), key=lambda x: x[1], reverse=True)
23     # 生成图
24     wc.add("", word_list, word_size_range=[20, 100], shape=SymbolType.DIAMOND)
25     wc.set_global_opts(title_opts=opts.TitleOpts(title="福利分析"))
26     return wc

  4、可视化处理 

  学历岗位占比图   

 1 left_num = 10
 2 rigth_num = 30
 3 for x in range(0, len(value)):
 4     pie.add(
 5         "",
 6         [[attr[x], round(value[x] / val_sum * 100, 2)], ["剩余", round((val_sum - value[x]) / val_sum * 100, 2)]],
 7         center=[str(left_num) + '%', str(rigth_num) + '%'],
 8         radius=[60, 80],
 9         label_opts=new_label_opts()
10     )
11     left_num += 20
12     if left_num > 90:
13         left_num = 10
14         rigth_num += 40
15 pie.set_global_opts(
16     title_opts=opts.TitleOpts(title="学历岗位数对比"),
17     legend_opts=opts.LegendOpts(
18         type_="scroll", pos_top="5%"
19     ),
20 )

    Python高级应用程序设计任务_第5张图片

    

    城市地图岗位对应图

 1 map = Map({"theme": ThemeType.MACARONS})
 2 map.add(
 3     "岗位数",
 4     [list(z) for z in zip(attr, value)],
 5     "china"
 6 )
 7 map.set_global_opts(
 8     title_opts=opts.TitleOpts(title="地区岗位数对比"),
 9     visualmap_opts=opts.VisualMapOpts(max_=200),
10 )

    Python高级应用程序设计任务_第6张图片

    

    工资区间柱状图

1 label = [
2     '3k以下', '3k-5k', '5k-8k', '8k-12k', '12k-15k', '15k-20k', '20k-25k', '25k以上'
3 ]
4 bar = Bar()
5 bar.add_xaxis(label)
6 bar.add_yaxis('智联', pin_value)
7 
8 bar.set_global_opts(title_opts=opts.TitleOpts(title="平台岗位工资对比"))

    Python高级应用程序设计任务_第7张图片

 

  5、数据持久化

    采用csv方式储存

1 with open("智联全国数据.csv", "w", newline='', encoding='utf-8') as datacsv:
2     # dialect为打开csv文件的方式,默认是excel,delimiter="\t"参数指写入的时候的分隔符
3     csvwriter = csv.writer(datacsv, dialect=("excel"))
4     # csv文件插入一行数据
5     csvwriter.writerow(['抓取来源', '岗位名称', '薪资', '工作地点', '工作经验', '学历要求', '公司名称', '公司行业', '公司类型', '公司规模', '公司福利'])
6     for temp in page_data:
7         # csv文件插入一行数据
8         csvwriter.writerow(temp)

    Python高级应用程序设计任务_第8张图片

 

    6、最终代码  

  1 import requests
  2 import time
  3 import json
  4 import csv
  5 import re
  6 import random
  7 import openpyxl
  8 
  9 
 10 def start_zl(city_name, city_code, keyword):
 11     print("抓取城市:" + city_name + "_" + city_code + "信息中")
 12     page = 0
 13     max = None
 14     result = []
 15     while True:
 16         print('当前{}页中'.format(page + 1))
 17         next_page = page * 90
 18         now_time = int(time.time() * 1000)
 19         url = "https://fe-api.zhaopin.com/c/i/sou?" \
 20               "pageSize=90" \
 21               "&cityId={}" \
 22               "&salary=0,0&workExperience=-1&education=-1&companyType=-1&employmentType=-1&jobWelfareTag=-1&" \
 23               "kw={}" \
 24               "&start={}" \
 25               "&kt=3&_v={}" \
 26               "&x-zp-page-request-id=84cec3861cb34e77a0f76870970d07c1-{}-988542" \
 27               "&x-zp-client-id=cd61dab4-7d6d-4c58-a963-b9a4c1474d9f".format(city_code, keyword, next_page,
 28                                                                             random.random(), now_time)
 29         while True:
 30             try:
 31                 rsp = requests.get(url=url, headers=headers)
 32                 desc_json = json.loads(rsp.text)
 33                 if desc_json['data']['numTotal'] == 0:
 34                     max = 0
 35                     break
 36                 # 获取页数
 37                 if max is None:
 38                     count = desc_json['data']['numTotal']
 39                     if count <= 90:
 40                         max = 1
 41                     else:
 42                         if count % 90 == 0:
 43                             max = count // 90
 44                         else:
 45                             max = count // 90 + 1
 46                 for item in desc_json['data']['results']:
 47                     try:
 48                         result.append(
 49                             ['zhilian', item['jobName'], item['salary'], city_name,
 50                              item['workingExp']['name'], item['eduLevel']['name'], item['company']['name'],
 51                              item['jobType']['items'][0]['name'], item['company']['type']['name'],
 52                              item['company']['size']['name'], '_'.join(item['welfare'])])
 53                     except Exception as e:
 54                         continue
 55                 break
 56             except Exception as e:
 57                 print(url)
 58                 print("错误:", e, ", 返回结果:", rsp.text)
 59         if page >= max:
 60             break
 61         page += 1
 62 
 63     return result
 64 
 65 
 66 # 构建待抓取url列表
 67 def get_province():
 68     # 随意访问一个网址
 69     start_url = 'https://sou.zhaopin.com/?p=12&jl=489&sf=0&st=0&kw=%E8%BD%AF%E4%BB%B6&kt=3'
 70     # 发起请求
 71     rsp = requests.get(url=start_url, headers=headers)
 72     # 正则匹配城市信息
 73     city_str = re.search('__INITIAL_STATE__=(.*?)', rsp.text, re.S).group(1)
 74     # 解析成json格式
 75     city_json = json.loads(city_str)
 76     province = city_json['basic']['dict']['location']['province']
 77     return province
 78 
 79 
 80 if __name__ == "__main__":
 81     headers = {
 82         "User-Agent": 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36'
 83     }
 84     # 获取城市信息
 85     province = get_province()
 86     print(province)
 87     # 搜索关键词
 88     keyword = 'python'
 89 
 90     # 存放抓取结果
 91     page_data = []
 92     for item in province:
 93         for sub in item['sublist']:
 94             # 启动爬虫
 95             result = start_zl(item['name'], sub['code'], keyword)
 96             if len(result) != 0:
 97                 page_data.extend(result)
 98 
 99     with open("智联全国数据.csv", "w", newline='', encoding='utf-8') as datacsv:
100         # dialect为打开csv文件的方式,默认是excel
101         csvwriter = csv.writer(datacsv, dialect=("excel"))
102         # csv文件插入一行数据
103         csvwriter.writerow(['抓取来源', '岗位名称', '薪资', '工作地点', '工作经验', '学历要求', '公司名称', '公司行业', '公司类型', '公司规模', '公司福利'])
104         for temp in page_data:
105             # csv文件插入一行数据
106             csvwriter.writerow(temp)
107 
108     print("抓取完成")
  1 from pyecharts.charts import Page, Bar, Map, WordCloud, Pie, Line
  2 from pyecharts import options as opts
  3 import pandas as pd
  4 from pyecharts.commons.utils import JsCode
  5 from pyecharts.globals import ThemeType, SymbolType
  6 import re
  7 import jieba
  8 
  9 
 10 # 学历分布饼图
 11 def pie_multiple():
 12     # 格式化学历信息
 13     education = []
 14     for c in df['学历要求']:
 15         if '' in c:
 16             c = '其他'
 17         elif c == '中专' or c == '中技':
 18             c = '中专/中技'
 19         education.append(c)
 20     df['education'] = education
 21     # 按楼层分组
 22     df_education = df.groupby(df['education'])
 23     df_education = df_education.size()
 24 
 25     value = df_education.values
 26     attr = list(df_education.keys())
 27     print(attr)
 28     val_sum = sum(value)
 29     pie = Pie({"theme": ThemeType.MACARONS})
 30     # 格式化显示格式
 31     fn = """
 32         function(params) {
 33             if(params.name == '剩余')
 34                 return '\\n\\n\\n' + params.name + ' : ' + params.value + '%';
 35             return params.name + ' : ' + params.value + '%';
 36         }
 37         """
 38 
 39     def new_label_opts():
 40         return opts.LabelOpts(formatter=JsCode(fn), position="center")
 41 
 42     left_num = 10
 43     rigth_num = 30
 44     for x in range(0, len(value)):
 45         pie.add(
 46             "",
 47             [[attr[x], round(value[x] / val_sum * 100, 2)], ["剩余", round((val_sum - value[x]) / val_sum * 100, 2)]],
 48             center=[str(left_num) + '%', str(rigth_num) + '%'],
 49             radius=[60, 80],
 50             label_opts=new_label_opts()
 51         )
 52         left_num += 20
 53         if left_num > 90:
 54             left_num = 10
 55             rigth_num += 40
 56     pie.set_global_opts(
 57         title_opts=opts.TitleOpts(title="学历岗位数对比"),
 58         legend_opts=opts.LegendOpts(
 59             type_="scroll", pos_top="5%"
 60         ),
 61     )
 62     return pie
 63 
 64 
 65 # 岗位覆盖地区图
 66 def map_visualmap():
 67     # 格式化地区信息
 68     county = []
 69     for cou in df['工作地点']:
 70         cou = cou[:2]
 71         county.append(cou)
 72     df['county'] = county
 73     # 按楼层分组
 74     df_county = df.groupby(df['county'])
 75     # 获取分组后的数据
 76     county_size = df_county.size()
 77     value = county_size.values
 78     value = [int(val) for val in value]
 79     attr = list(county_size.keys())
 80 
 81     map = Map({"theme": ThemeType.MACARONS})
 82     map.add(
 83         "岗位数",
 84         [list(z) for z in zip(attr, value)],
 85         "china"
 86     )
 87     map.set_global_opts(
 88         title_opts=opts.TitleOpts(title="地区岗位数对比"),
 89         visualmap_opts=opts.VisualMapOpts(max_=200),
 90     )
 91     return map
 92 
 93 
 94 # 工资区间柱状图
 95 def bar_img():
 96     fanwei = [0, 3000, 5000, 8000, 12000, 15000, 20000, 25000, 100000]
 97     label = [
 98         '3k以下', '3k-5k', '5k-8k', '8k-12k', '12k-15k', '15k-20k', '20k-25k', '25k以上'
 99     ]
100     bar = Bar()
101     bar.add_xaxis(label)
102 
103     pattern = '\d+'
104     # 去除 "薪资面议" 等无效数据
105     df.drop(df[df['薪资'].str.contains('面议')].index, inplace=True)
106 
107     salary_list = df['薪资'].values
108     # 将字符串转化为列表
109     df['salary'] = df['薪资'].str.findall(pattern)
110     avg_salary = []
111     # 统计平均工资
112     for salary in salary_list:
113         k = re.findall(pattern, salary)
114         int_list = [int(n) for n in k]
115         # 取工资区间平均值
116         if len(int_list) == 2:
117             avg_wage = (int_list[1] + int_list[0]) / 2
118         else:
119             avg_wage = int_list[0]
120         # 去除无效数据
121         if 0 < avg_wage < 100:
122             avg_salary.append(avg_wage * 1000)
123 
124     df_avg = pd.DataFrame({'avg': avg_salary})
125     # 对工资进行分组 比如:0-3000, 3000-5000这种
126     fenzu = pd.cut(df_avg['avg'].values, fanwei, right=False)  # 分组区间
127     pin_shu = fenzu.value_counts()  # series,区间-个数
128     pin_value = pin_shu.values
129     pin_value = [int(pin) for pin in pin_value]
130 
131     bar.add_yaxis('智联', pin_value)
132 
133     bar.set_global_opts(title_opts=opts.TitleOpts(title="平台岗位工资对比"))
134     return bar
135 
136 
137 # 公司福利词云图
138 def word_cloud():
139     wc = WordCloud({"theme": ThemeType.MACARONS})
140     word_dict = {}
141     for word in df['公司福利']:
142         if 'nan' in str(word):
143             continue
144         r = r"[0-9\s+\.\!\/_,$%^*()?;;:-【】+\"\']+|[+——!,;:。?、~@#¥%……&*()]+"
145         # 去除特殊符号
146         word = re.sub(r, '', str(word), re.S)
147         # cut分词,并去重
148         words = set(jieba.cut(word, cut_all=False))
149         # 统计词频
150         for word in words:
151             if len(word) < 2:
152                 continue
153             if word not in word_dict:
154                 word_dict[word] = 1
155             else:
156                 word_dict[word] += 1
157     # 词频排序
158     word_list = sorted(word_dict.items(), key=lambda x: x[1], reverse=True)
159     # 生成图
160     wc.add("", word_list, word_size_range=[20, 100], shape=SymbolType.DIAMOND)
161     wc.set_global_opts(title_opts=opts.TitleOpts(title="福利分析"))
162     return wc
163 
164 
165 if __name__ == "__main__":
166     # 读取excel文件
167     df = pd.read_csv('智联全国数据.csv', encoding='utf-8')
168 
169     # 自定义画布,多图合一
170     page = Page(layout=Page.SimplePageLayout)
171 
172     # 添加到页面
173     page.add(pie_multiple(), map_visualmap(), bar_img(), word_cloud())
174     # page.add()
175 
176     page.render('智联可视化.html')

四、结论(10分)

  1、经过对主题数据的分析与可视化,可以得到哪些结论?

    1、python岗位,果然还是大城市就业好,北上广相对其他地区,明显好就业些

    2、虽然说研究生以上文凭要好很多,但是针对学历这块,本科文凭还是很吃香,主要集中是在这一块

    3、通过工资图发现,一般python岗位的工资集中在5k-20k间,其中8k-12k的岗位数最多

    4、而公司福利方面,基本每家公司都有五险一金这些基本福利,还是不错的

  2、对本次程序设计任务完成的情况做一个简单的小结

  通过这次的爬虫作业,正好也想看下关于python的就业情况,也算有了个了解,针对代码方面,还是有很多没明白的,但是也学习到了很多知识,以后会继续加油。

你可能感兴趣的:(Python高级应用程序设计任务)