欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python爬虫之薪资分析

程序员文章站 2022-03-04 19:05:04
...

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爬虫之薪资分析