基于bs4的拉勾网AI相关工作爬虫实现及数据分析

这是我在简书上的第二篇文章,这篇文章在微信公众号里面分两次写完的,大家可以关注我的微信公众号(机器学习和数学),第一时间阅读我的文章哦~。

这篇文章主要是我如何抓取拉勾上面AI相关的职位数据,其实抓其他工作的数据原理也是一样的,只要会了这个,其他的都可以抓下来。一共用了不到100行代码,主要抓取的信息有“职位名称”,“月薪”,“公司名称”,“公司所属行业”,“工作基本要求(经验,学历)”,“岗位描述”等。涉及的工作有“自然语言处理”,“机器学习”,“深度学习”,“人工智能”,“数据挖掘”,“算法工程师”,“机器视觉”,“语音识别”,“图像处理”等几大类。

下面随便截个图给大家看下,我们想要的信息


基本信息

image.png

然后看下我们要的信息在哪里


职位描述

然后职位详细信息是的url就在那个href里面,所以关键是要取到那个href就OK了。

下面直接上代码

首先我们需要判断一个url是不是合法的url,就是isurl方法。

urlhelper方法是用来提取url的html内容,并在发生异常时,打一条warning的警告信息

import urllib.request
from bs4 import BeautifulSoup
import pandas as pd
import requests
from collections import OrderedDict
from tqdm import tqdm, trange
import urllib.request
from urllib import error
import logging


logging.basicConfig(level=logging.WARNING)


def isurl(url):
    if requests.get(url).status_code == 200:
        return True
    else:
        return False


def urlhelper(url):
    try:
        req = urllib.request.Request(url)
        req.add_header("User-Agent",
                       "Mozilla/5.0 (Windows NT 6.1; WOW64)"
                       " AppleWebKit/537.36 (KHTML, like Gecko) "
                       "Chrome/45.0.2454.101 Safari/537.36")
        req.add_header("Accept", "*/*")
        req.add_header("Accept-Language", "zh-CN,zh;q=0.8")
        data = urllib.request.urlopen(req)
        html = data.read().decode('utf-8')

        return html
    except error.URLError as e:
        logging.warning("{}".format(e))

下面就是爬虫的主程序了,里面需要注意的是异常的处理,很重要,不然万一爬了一半挂了,前面爬的又没保存就悲剧了。还有一个是想说BeautifulSoup这个类真的是十分方便,熟练使用能节省很多时间。


names = ['ziranyuyanchuli', 'jiqixuexi', 'shenduxuexi', 'rengongzhineng',
         'shujuwajue', 'suanfagongchengshi', 'jiqishijue', 'yuyinshibie',
         'tuxiangchuli']
fw = open("./datasets/lagou/job.txt", 'a+')
for name in tqdm(names):
    savedata = []
    page_number = 0
    for page in range(1, 31):

        rooturl = 'https://www.lagou.com/zhaopin/{}/{}/'.format(name, page)
        if not isurl(rooturl):
            continue
        html = urlhelper(rooturl)
        soup = BeautifulSoup(html, "lxml")
        resp = soup.findAll('div', attrs={'class': 's_position_list'})
        if len(resp) == 1:
            resp = resp[0]
            resp = resp.findAll('li', attrs={'class': 'con_list_item default_list'})
            page_number += 1
            if page_number % 5 == 0:
                print(page_number)
                # 保存到本地
                df = pd.DataFrame(savedata)
                df.to_csv("./datasets/lagou/{}_{}.csv".format(name, page_number), index=None)
                savedata = []
            for i in trange(len(resp)):
                position_link = resp[i].findAll('a', attrs={'class': 'position_link'})
                link = position_link[0]['href']
                if isurl(link):
                    htmlnext = urlhelper(link)
                    soup = BeautifulSoup(htmlnext, "lxml")
                    try:
                        # 职位描述
                        job_bt = soup.findAll('dd',
                                              attrs={'class': 'job_bt'})[0].text
                    except:
                        continue
                    try:
                        # 工作名称
                        jobname = position_link[0].find('h3').get_text()
                    except:
                        continue
                    try:
                        # 工作基本要求
                        p_bot = resp[i].findAll('div',
                                                attrs={'class': 'p_bot'})[0].text
                    except:
                        continue
                    try:
                        # 月薪
                        money = resp[i].findAll('span',
                                                attrs={'class': 'money'})[0].text
                    except:
                        continue
                    try:
                        # 行业
                        industry = resp[i].findAll('div',
                                                   attrs={'class': 'industry'})[0].text
                    except:
                        continue
                    try:
                        # 公司名字
                        company_name = resp[i].findAll(
                            'div', attrs={'class': 'company_name'})[0].text
                    except:
                        continue
                    rows = OrderedDict()
                    rows["jobname"] = jobname.replace(" ", "")
                    rows["money"] = money
                    rows["company_name"] = company_name.replace("\n", "")
                    rows["p_bot"] = p_bot.strip().replace(" ", ""). \
                        replace("\n", ",").replace("/", ",")
                    rows["industry"] = industry.strip(). \
                        replace("\t", "").replace("\n", "")
                    rows["job_bt"] = job_bt
                    savedata.append(rows)

最终修改的代码,主要是增加了异常处理,异常处理在爬虫中真的很重要,不然中间挂了,就很尴尬,还有就是数据保存的间隔,没爬5页就保存一次,防止爬虫中断,前功尽弃。

接下来,继续分析一下爬下来的数据
主要分析的是岗位职责和岗位要求,基本思路是先分词,然后统计词频,最后最词云展示出来。先看下效果


词云

从这个图可以看出来,自然语言处理大多数需要掌握深度学习,需要用深度学习去解决问题,然后是工作经验,项目经验,以及对算法的理解。

首先分词,要正确分词,需要有一份高质量的词典,因为在岗位描述里面有好多专有名词,比如深度学习,命名实体识别,词性标注等等。我还是使用的jieba来做分词,结巴对这些词是分不出来的,所以先要建一个词典,我选了大概100个左右,然后加上公司的名字,一共400个左右。这个也可以分享给大家,非常欢迎大家补充,建立一份高质量的AI领域的专业词典,其实是非常有意义的事情,对这方面的文本分析非常有帮助。

对分词过程中标点符号的处理,有2种办法,一种是先去标点,然后分词,还有一种是先分词,然后去标点。常用的做法是先分词,然后把标点符号放在stopwords里面,这次我没有这么做,我是先按照可以划分句子、短语结构的标点符号,先把句子做切割,比如句号,一般以句号分割的两句话之间,肯定不会是一个词。最后对切割完毕的句子做分词,这样可以提高准确率,能防止分错不少词。分词的时候先把不能分割语义的标点符号先去掉,然后分词。

接下来,对上面切割好的词,统计词频,做一个词云,这里生成的词云可以做成那个样子,是因为我把本文开头的那个图片,作为背景图片,用wordcloud生成的词云就会是那个样子的。

好,直接看代码:

import os
import pandas as pd
import re
import jieba
import matplotlib.pyplot as plt
import wordcloud
from collections import OrderedDict
from tqdm import tqdm

from scipy.misc import imread
sw = []
jieba.load_userdict("./dictionary/ai.dict")


def linesplit_bysymbol(line):
    # 先按句子切割,然后去标点,
    line = line.replace("\n", "").replace("\r", '')
    out = []
    juzi = r"[\]\[\,\!\】\【\:\,\。\?\?\)\(\(\『\』\<\>\、\;\.\[\]\(\)\〔\〕\+\和\的\与\在]"
    p = r"[\^\$\]\/\.\’\~\#\¥\#\&\*\%\”\“\]\[\&\×\@\]\"]"

    salary_pattern = r'\d+\k\-\d+\k'
    salarys = re.compile(salary_pattern).findall(line)

    for salary in salarys:
        out.append(salary)
    linesplit = re.split(juzi, line)
    for x in linesplit:

        if str(x).isnumeric():
            continue
        if len(x) < 1:
            continue
        if x == '职位描述':
            continue
        if x == '岗位要求':
            continue
        if x == '岗位职责':
            continue
        if x == '工作职责':
            continue
        if x == '岗位说明':
            continue
        res = re.sub(p, "", x)
        rescut = jieba.lcut(res)
        for w in rescut:
            if str(w).isdigit():
                continue
            out.append(w)
    return " ".join(out)


def analysis(job='suanfagongchengshi'):
    path = './datasets/lagou'
    res = []
    for file in tqdm(os.listdir(path)):
        if "{}".format(job) in file:
            file_name = os.path.join(path, file)
            data = pd.read_csv(file_name, usecols=['job_bt', 'company_name']).values
            for x in data:
                rows = OrderedDict()
                rows['company'] = x[0]
                rows['bt'] = linesplit_bysymbol(x[1])
                res.append(rows)
    df = pd.DataFrame(res)
    df.to_csv("./datasets/lagou/{}.csv".format(job), index=None)

下面是做词云的代码:


def plot_word_cloud(file_name, savename):
    text = open(file_name, 'r', encoding='utf-8').read()

    alice_coloring = imread("suanfa.jpg")
    wc = wordcloud.WordCloud(background_color="white", width=918, height=978,
                             max_font_size=50,
                             mask=alice_coloring,
                             random_state=1,
                             max_words=80,
                             mode='RGBA',
                             font_path='msyh.ttf')
    wc.generate(text)
    image_colors = wordcloud.ImageColorGenerator(alice_coloring)

    plt.axis("off")
    plt.figure()
    plt.imshow(wc.recolor(color_func=image_colors))
    plt.axis("off")
    plt.figure(dpi=600)
    plt.axis("off")
    wc.to_file(savename)

最后,给大家提供一份我建的词典,欢迎大家补充哈~

链接:https://pan.baidu.com/s/1kWPshAR 密码:fvuy
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358

推荐阅读更多精彩内容