Python爬虫之薪资分析

Python爬虫之薪资分析

准备环境

  • python3
  • BeautifulSoup
  • PyCharm
  • Echart

背景

想看看智联招聘上各个行业的评价薪资是多少,最后生成个图表,最好还能排除培训机构,因为培训机构并不招人但是招聘广告上的工资却很高….

最终效果

《Python爬虫之薪资分析》

从图上看出,我们会把需要行业的招聘信息抓取下来,然后讲他们的平均薪资记录下来生成柱状图,当点击其中的柱状图的时候,可以显示这个岗位的薪资分布

整体流程

整体会由以下几个模块组成:
– url_manager: 用来管理所有的url的
– htmldownloader: 根据url将页面上的数据下载下来
– html_parser: 根据下载下来的数据,来解析出我们需要的平均薪资,并且排除掉一些常见的培训机构
– html_outter: 将最后统计的结果输出成html页面
– spider_main: 入口,并负责启动各个页面

流程

spider_main来初始化所有的模块 => 从url_manager中取出一个url => 将url交给html_downloader来下载html => 将下载好的页面交给html_parser来进行解析 => 最后将解析好的结果通过html_outter来输出成html页面

模块详情

html_manager

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import urllib.parse


class UrlManager(object):

    def __init__(self, keys, p):
        self.urls = set()
        # 基础URL没有搜索的关键字
        self.base_url = 'http://sou.zhaopin.com/jobs/searchresult.ashx?jl=%E5%A4%A7%E8%BF%9E&&isadv=0'
        for key in keys:
            for i in range(1, p + 1):
                self.add_url(self.base_url, key, i)

    def add_url(self, url, key, p):
        key = urllib.parse.quote(key)  # url编码
        url = '%s&kw=%s&p=%d' % (url, key, p)
        print(url)
        self.urls.add(url)
        pass

    def get_url(self):
        """ :return: 未抓取的url """
        return self.urls.pop()

    def has_new_url(self):
        """ :return: 是否还有url没有被抓取 """
        return len(self.urls) != 0
  • 在初始化的时候,根据基础网址来拼接要抓取的关键字,和页数来生成指定的url
  • 所有的url放在一个set中进行存储
  • 当从集合中获取url的时候,会将这个url从集合中删除

html_downloader

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import urllib.request


class HtmlDownloader(object):
    def download(self, url):
        if url:
            req = urllib.request.Request(url)
            req.add_header("User-Agent",
                           "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36")
            with urllib.request.urlopen(req) as opener:
                return opener.read().decode('utf-8')
  • 这里使用urllib.request来进行网络请求
  • 添加User-Agent头来假装我们是一个浏览器
  • 最后在读取信息的时候需要decode(‘utf-8’)

html_parser

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from bs4 import BeautifulSoup
import urllib.parse

black_words = ['达内', '睿道', '文思海辉', '中软', '鹏讯']


class HtmlParser(object):

    def parser(self, page_url, html_cont):
        soup = BeautifulSoup(html_cont, 'html.parser')

        moneys = []

        result = urllib.parse.urlparse(page_url)
        params = urllib.parse.parse_qs(result.query, True)

        rows = soup.find_all('tr', class_="")
        for row in rows:
            try:
                company_node = row.find('td', class_="gsmc").find('a')
                company_name = company_node.get_text()
                for black in black_words:
                    if black in company_name:
                        print("黑名单:", company_name)
                        continue

                money_node = row.find('td', class_="zwyx")
                money = money_node.get_text()
                money_range = money.split('-')
                if len(money_range) == 2:
                    moneys.append(int(money_range[0]) + int(money_range[1]) / 2)

            except Exception as e:
                print("-------------")
                print(row)
                print("-------------------")
                print('can not parser:', e)

        return params['kw'][0], moneys
  • 使用BeautifulSoup来进行解析html页面,在使用之前需要安装BeautifulSoup
  • 在解析的过程中是有可能失败的,所以使用try来捕获异常防止程序崩溃
  • 在解析的时候,不光要解析薪资,还需要解析出公司的名字,如果包含培训机构的名字,我们就不统计它
  • 解析的时候其实就是去找网页的规律
    《Python爬虫之薪资分析》
  • 由于薪资都是个范围,所以在统计的时候取上限,和下限的平均值
  • 还有些薪资是面议,就不统计了
  • 最后将这一页的数据收集起来并返回

html_outter

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


class HtmlOuter(object):

    def __init__(self):
        self.datas = {}

    def collect_data(self, k, m):

        if k not in self.datas:
            self.datas[k] = []
        self.datas[k] = self.datas[k] + m

    def output_html(self):
        fout = open('out/output.html', 'wb')
        fin = open('template/output_t.html', 'rb')

        result = []
        for k, v in self.datas.items():
            num = len(v) if len(v) != 0 else 1

            result.append([k, sum(v) / num])
            self._output_detail(k, v)

        print(result)

        for line in fin.readlines():
            line = line.decode("utf-8")

            line = line.replace('{{data}}', repr(result))

            fout.write(line.encode("utf-8"))

        fin.close()
        fout.close()

    def _output_detail(self, key, values):
        fout = open('out/%s.html' % key, 'wb')
        fin = open('template/detail_t.html', 'rb')

        result = [['5000以下', 0], ['5000-7000', 0], ['7000-10000', 0], ['10000以上', 0]]
        for value in values:
            if value < 5000:
                result[0][1] = result[0][1] + 1
            elif value < 7000:
                result[1][1] = result[1][1] + 1
            elif value < 10000:
                result[2][1] = result[2][1] + 1
            else:
                result[3][1] = result[3][1] + 1

        for line in fin.readlines():
            line = line.decode("utf-8")
            line = line.replace('{{data}}', repr(result))
            line = line.replace('{{title}}', key)
            fout.write(line.encode('utf-8'))

        fin.close()
        fout.close()
  • 输出的时候需要收集每一页的信息
  • 最后输出的时候实际上是先写好模板,然后将指定的字符串替换成数据

html模板

output_t.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="echarts.common.min.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('main')); // 指定图表的配置项和数据 var option = { title: { text: '薪资分布', left: 'center', top: 20, textStyle: { color: '#000000' } }, tooltip: {}, legend: {}, // 全局调色盘。 xAxis: {type: 'category'}, yAxis: {}, dataset: { source: {{data}} }, series: [ {type: 'bar', color: '#c23531', // 高亮样式。 emphasis: { itemStyle: { // 高亮时点的颜色。 color:'#ea926b' }, label: { show: true, // 高亮时标签的文字。 color:'#314134' } }} ] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); //点击事件 myChart.on('click',function (params) { window.location.href='/CollectSalary/out/'+params.name+'.html' }) </script>
</body>
</html>

detail_t.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="echarts.common.min.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript"> // 基于准备好的dom,初始化echarts实例 var myChart = echarts.init(document.getElementById('main')); // 指定图表的配置项和数据 var option = { backgroundColor: '#2c343c', title: { text: '{{title}}', left: 'center', top: 20, textStyle: { color: '#ccc' } }, // 全局调色盘。 color: ['#c23531','#2f4554', '#61a0a8', '#d48265', '#91c7ae','#749f83', '#ca8622', '#bda29a','#6e7074', '#546570', '#c4ccd3'], dataset: { source: {{data}} }, tooltip: { trigger: 'item', formatter: "{a} <br/>{b} : {c} ({d}%)" }, visualMap: { show: false, min: 80, max: 600, inRange: { colorLightness: [0, 1] } }, series: [ { name: '职位个数', type: 'pie', radius: '55%', center: ['50%', '50%'], label: { normal: { textStyle: { color: 'rgba(255, 255, 255, 0.3)' } } }, labelLine: { normal: { lineStyle: { color: 'rgba(255, 255, 255, 0.3)' }, smooth: 0.2, length: 10, length2: 20 } }, animationType: 'scale', animationEasing: 'elasticOut', animationDelay: function (idx) { return Math.random() * 200; } } ] }; // 使用刚指定的配置项和数据显示图表。 myChart.setOption(option); </script>
</body>
</html>

另外 这里是直接用pyCharm打开的网页,不是打开的静态页面

spider_main

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import html_downloader
import html_outter
import html_parser
import url_manager


class SpiderMain(object):
    def __init__(self, keys, p):
        self.urls = url_manager.UrlManager(keys, p)
        self.downloader = html_downloader.HtmlDownloader()
        self.parser = html_parser.HtmlParser()
        self.outer = html_outter.HtmlOuter()

    def craw(self):
        while self.urls.has_new_url():
            new_url = self.urls.get_url()
            html_content = self.downloader.download(new_url)
            k, m = self.parser.parser(new_url, html_content)
            print(f'craw: {new_url}')
            self.outer.collect_data(k, m)
        self.outer.output_html()


if __name__ == '__main__':
    key_words = ['JAVA', "Python", "运维", "Android", "大数据"]
    pages = 5
    SpiderMain(key_words, pages).craw()
  • 在初始化的时候,输入想要抓取的关键字和要抓取的页数
  • 这里没有抓取全部的,而是抓取了几页,如果一共岗位没有那么多的话,html是没有数据的,所以不会影响结果

目录结构:
《Python爬虫之薪资分析》

    原文作者:cfy137000
    原文地址: https://blog.csdn.net/cfy137000/article/details/79779862
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞