本文主要讲Python最常见的应用之一——网络数据获取,即爬虫:
先介绍了网页和网络的基础知识,为从网页中获取数据打好基础;接下来以两个案例介绍从网络中获取数据和处理数据的不同方式,以进一步认识Python爬虫和数据处理。
数据源有很多,可以从数据库中获取,可以从文件中获取,也可以从网络中获取,也可以直接获取裸数据。
数据库数据来源有很多,比如RDBMS,即关系数据库管理系统,属于结构化数据,具体包括MySQL、PostgreSQL、SQLServer、Oracle、SQLite等数据库类型。
数据文件包括:
Excel
最常见,最有问题。
分隔格式
最常见、最受欢迎。
包括逗号分隔符(csv)、制表符分隔符(tsv)、|分隔符等形式。
问题包括数据字段中的分隔、编码等。
如下:
JSON
即JavaScript Object Notation(JavaScript对象表示法)的简写,属于半结构化数据。
属性位于冒号的左侧,数值位于冒号右侧,属性用逗号分隔,多值属性作为层次值。
如下:
XML
Extensible Markup Language(可扩展标记语言)的简写,属于半结构化数据,也是最常见的数据交换。
如下:
网络数据:
主要为HTML,为非结构化数据。
如下:
网络数据传输,一般是先请求、再响应,中间可能经过很多次层转发,计算机理论的OSI模型如下:
每一层的过程可能如下:
现在一般的网站模式为客户端/服务器,客户端一般即为自己所使用的浏览器,服务器是存储网站资源、管理网络请求的平台。
一般的请求过程如下:
(1)用户输入URL;
(2)客户端发送请求Request;
(3)服务器接收请求Request;
(4)服务器返回响应Response Back;
(5)客户端接收并解析Response。
对于一个url,如https://127.0.0.1:8000/hello,http
表示协议,127.0.0.1
表示主机号,8000
是端口号,/hello
是路径,从而可以精确定位到要访问的信息。
使用浏览器访问网站的基本操作如下:
可以看到,在进行搜索和筛选时,链接也会有所变化,以向浏览器请求不同的内容。
还可以使用浏览器的审计工具,可以查看页面元素、网络请求、样式等。
如下:
可以看到,页面中的内容都是通过很多标签和样式组织起来的,这就是HTML代码;
同时在请求和相应的时候,都携带了很多参数。
进一步使用审计工具如下:
可以看到,可以通过设置实现模拟不同的设备进行请求,此时请求参数的User-Agent
参数也随之变化。
一个HTTP请求包括请求方法、请求路径和HTTP版本,一个HTTP响应包括HTTP版本、状态码和响应体。
网页是由HTML代码组成的,信息一般包含在这些代码中;
CSS是一些样式文件,对于获取数据影响不大;
JavaScript代码可以执行一些更复杂的逻辑,对获取数据的影响可能比较大。
一个简单的HTML代码如下:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
head>
<body>
<h1>首页h1>
<form action="" method="post">
<table>
<tr>
<td><input type="text" name="name">td>
<td><input type="submit" value="提交">td>
tr>
table>
form>
body>
html>
网页抓取一般有两种方式:
逐行扫描Line by Line
包括简单字符串处理和正则表达式方式等。
正则表达式是一个特殊的字符序列,它能方便检查一个字符串是否与某种模式匹配,Python中的re模块使Python拥有全部的正则表达式功能,其中,正则表达式的原理如下:
具体使用可参考https://www.runoob.com/python/python-reg-expressions.html。
树形模型Tree Model
利用HTML的树形结构来获取HTML中的信息,包括BeautifulSoup、lxml等库支持该功能。
网络请求HTML并展示为树形结构的过程如下:
其中,date数据为Sep 13, 2014
,message数据为i didnt know that
。
如果使用正则表达式提取这两个数据,方式为
和(.+)<\/h2>
<\/span>(.+)<\/li>
;
而使用属性模型如BeautifulSoup提取数据,会建立如下的结构:
从而,提取数据的方式为div.h2.text
和div.ul.li.text
。
以BOSS直聘https://www.zhipin.com/为例,实现较完整的网络数据抓取的过程。
网站预览如下:
可以看到,在查看器中选择一定的HTML代码区域,页面中也会有相应的高亮显示,说明需要获取的数据也就在这些对应的HTML代码中;
每一条结果的位置为class为job-list的div下面的ul下面的li下面的class为job-primary的div,有多少条职位信息就有多少个li,其中一个li的内容如下:
<li>
<div class="job-primary">
<div class="info-primary">
<div class="primary-wrapper">
<div class="primary-box" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html"
data-jid="7271f2f28169375a1nR42t-6GFpQ" data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1"
data-jobid="102127880" data-index="0" ka="search_list_1" target="_blank">
<div class="job-title">
<span class="job-name"><a href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" title="数据分析"
target="_blank" ka="search_list_jname_1" data-jid="7271f2f28169375a1nR42t-6GFpQ"
data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1" data-jobid="102127880"
data-index="0">数据分析a>span>
<span class="job-area-wrapper">
<span class="job-area">北京·朝阳区·鸟巢span>
span>
<span class="job-pub-time">span>
div>
<div class="job-limit clearfix">
<span class="red">50-80K·14薪span>
<p>3-5年<em class="vline">em>本科p>
<div class="info-publis">
<h3 class="name"><img class="icon-chat"
src="https://z.zhipin.com/web/geek/resource/icon-chat-v2.png">曹先生<em
class="vline">em>数据挖掘h3>
div>
<button class="btn btn-startchat" href="javascript:;"
data-url="/wapi/zpgeek/friend/add.json?jobId=7271f2f28169375a1nR42t-6GFpQ&lid=nlp-aqyTkPDQjXA.search.1"
redirect-url="/web/geek/chat?id=495f7159c0c8664a1nFz39m8EA~~">
<img class="icon-chat icon-chat-hover"
src="https://z.zhipin.com/web/geek/resource/icon-chat-hover-v2.png" alt="">
<span>立即沟通span>
button>
div>
<div class="info-detail" style="top: 0px;">div>
div>
div>
<div class="info-company">
<div class="company-text">
<h3 class="name"><a href="/gongsi/33e052361693f8371nF-3d25.html" title="京东集团招聘"
ka="search_list_company_1_custompage" target="_blank">京东集团a>h3>
<p><a href="/i100001/" class="false-link" target="_blank"
ka="search_list_company_industry_1_custompage" title="电子商务行业招聘信息">电子商务a><em
class="vline">em>已上市<em class="vline">em>10000人以上p>
div>
<a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage_logo"
target="_blank"><img class="company-logo"
src="https://img.bosszhipin.com/beijin/mcs/bar/20191129/3cdf5ba2149e309b38868b62ae9c22cabe1bd4a3bd2a63f070bdbdada9aad826.jpg?x-oss-process=image/resize,w_100,limit_0"
alt="">a>
div>
div>
<div class="info-append clearfix">
<div class="tags">
<span class="tag-item">Excelspan>
<span class="tag-item">SPSSspan>
<span class="tag-item">Pythonspan>
<span class="tag-item">数据挖掘span>
<span class="tag-item">数据仓库span>
div>
<div class="info-desc">补充医疗保险,节日福利,定期体检,年终奖,餐补,交通补助,免费班车,包吃,股票期权,员工旅游,零食下午茶,五险一金,带薪年假div>
div>
div>
li>
可以看到,所需要的信息都在这些代码中。
还可以在页面中获取职位描述,如下:
并且进一步获取到完整的HTML代码如下:
<li>
<div class="job-primary">
<div class="info-primary">
<div class="primary-wrapper">
<div class="primary-box" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html?ka=search_list_1"
data-jid="7271f2f28169375a1nR42t-6GFpQ" data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1"
data-jobid="102127880" data-index="0" ka="search_list_1" target="_blank">
<div class="job-title">
<span class="job-name"><a href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" title="数据分析"
target="_blank" ka="search_list_jname_1" data-jid="7271f2f28169375a1nR42t-6GFpQ"
data-itemid="1" data-lid="nlp-aqyTkPDQjXA.search.1" data-jobid="102127880"
data-index="0">数据分析a>span>
<span class="job-area-wrapper">
<span class="job-area">北京·朝阳区·鸟巢span>
span>
<span class="job-pub-time">span>
div>
<div class="job-limit clearfix">
<span class="red">50-80K·14薪span>
<p>3-5年<em class="vline">em>本科p>
<div class="info-publis">
<h3 class="name"><img class="icon-chat"
src="https://z.zhipin.com/web/geek/resource/icon-chat-v2.png">曹先生<em
class="vline">em>数据挖掘h3>
div>
<button class="btn btn-startchat" href="javascript:;"
data-url="/wapi/zpgeek/friend/add.json?jobId=7271f2f28169375a1nR42t-6GFpQ&lid=nlp-aqyTkPDQjXA.search.1"
redirect-url="/web/geek/chat?id=495f7159c0c8664a1nFz39m8EA~~">
<img class="icon-chat icon-chat-hover"
src="https://z.zhipin.com/web/geek/resource/icon-chat-hover-v2.png" alt="">
<span>立即沟通span>
button>
div>
<div class="info-detail" style="top: -307.1px;">
<div class="info-detail-top">
<div class="detail-top-left">
<div class="detail-top-title">数据分析div>
<div class="detail-top-text">京东集团 · 数据挖掘: 曹先生div>
<a href="javascript:;" ka="popjob_interest_tosign_7271f2f28169375a1nR42t-6GFpQ"
data-url="/geek/tag/jobtagupdate.json?jobId=7271f2f28169375a1nR42t-6GFpQ&expectId=&tag=4&lid=nlp-aqyTkPDQjXA.search.1"
class="link-like " job-id="495f7159c0c8664a1nFz39m8EA~~">感兴趣a>
div>
<div class="detail-top-right detail-top-right2">
<div class="code-des">扫一扫,随时与BOSS开聊div>
<div class="code-icon">div>
div>
div>
<div class="detail-bottom">
<div class="detail-bottom-title">职位描述div>
<div class="detail-bottom-text">
职位描述<br>1、 分析研究用户画像,通过对海量数据的分析挖掘,提取用户特征、行为轨迹;<br>2、 参与算法研发工作,提升算法系统的性能和业务指标;<br>3、
梳理、对接不同业务线的临时数据需求,并抽象出定制化数据产品; <br>4、 结合项目需求,综合利用京东商城数据,搭建定制化指数模型;<br>5、
负责为产品运营提供数据分析支持,如产品分析、用户分析、运营分析等,并根据分析结果提出可落地的策略建议;<br>6、
积极推进跨部门合作,配合各类项目如期保质保量实施执行。<br>岗位要求<br>1、 本科及以上学历,统计学、数据、计算机相关专业优先考虑;<br>2、
两年及以上互联网数据分析从业经历,有电商类公司经验者优先,有综合指数构建经验者优先;<br>3、
能独立进行数据处理,撰写专项分析报告,掌握常用的分类、聚类、预测、关联规则、序列模式等挖掘算法;<br>4、
学习能力强,具备良好的沟通能力,能充分理解业务逻辑和目的,有清晰的数据分析思路和方法;<br>5、
数据敏感度高,善于从数据中发现问题,并可给出一定的解决方案;<br>6、
精通SQL、EXCEL,熟悉SPSS、SAS、Clementine、R、python等任一种专业数据分析工具,有Hadoop、Hive、Spark等使用经验者优先。<br>7、
有回归、聚类、分类、神经网络、NLP、最优化理论等相关理论基础和项目应用者优先
div>
div>
div>
div>
div>
<div class="info-company">
<div class="company-text">
<h3 class="name"><a href="/gongsi/33e052361693f8371nF-3d25.html" title="京东集团招聘"
ka="search_list_company_1_custompage" target="_blank">京东集团a>h3>
<p><a href="/i100001/" class="false-link" target="_blank"
ka="search_list_company_industry_1_custompage" title="电子商务行业招聘信息">电子商务a><em
class="vline">em>已上市<em class="vline">em>10000人以上p>
div>
<a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage_logo"
target="_blank"><img class="company-logo"
src="https://img.bosszhipin.com/beijin/mcs/bar/20191129/3cdf5ba2149e309b38868b62ae9c22cabe1bd4a3bd2a63f070bdbdada9aad826.jpg?x-oss-process=image/resize,w_100,limit_0"
alt="">a>
div>
div>
<div class="info-append clearfix">
<div class="tags">
<span class="tag-item">Excelspan>
<span class="tag-item">SPSSspan>
<span class="tag-item">Pythonspan>
<span class="tag-item">数据挖掘span>
<span class="tag-item">数据仓库span>
div>
<div class="info-desc">补充医疗保险,节日福利,定期体检,年终奖,餐补,交通补助,免费班车,包吃,股票期权,员工旅游,零食下午茶,五险一金,带薪年假div>
div>
div>
li>
还可以进一步访问职位详情如下:
先导入所需要的库,如下:
## Import the necessary packages
from bs4 import BeautifulSoup as bs
import urllib
import re
import pandas as pd
import requests
如需本节同步
ipynb
和数据文件,可以直接点击加QQ群 963624318 在群文件夹商业数据分析从入门到入职中下载即可。
使用requests库模拟请求:
response = requests.get('https://www.zhipin.com/job_detail/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&city=100010000&industry=&position=')
获取返回的相应内容,如下:
display(response.content[:300], response.text, response.encoding)
输出:
b'\n\n \n \n \n \n \xe8\xaf\xb7\xe7\xa8\x8d\xe5\x90'
'\n\n \n \n \n \n 请ç¨\x8då\x90\x8e \n \n \n \n \n \n \n BOSS\n æ\xad£å\x9c¨å\x8a\xa0è½½ä¸\xad...
\n \n \n \n \n \n\n'
'ISO-8859-1'
其中,response.content
用来获取相应的原始内容,response.text
用来获取经过编码渲染后的内容,response. encoding
是用于获取编码方式的。
但是显然,页面信息并没有显示完整,这是因为一般请求并不只是传入链接,还有一些其他的请求信息,如User-Agent、Referer、Cookie等。
如下:
header = {
'Cookie': 'lastCity=100010000; __g=-; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601602464; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601603899; __zp_stoken__=cb83bGgJhaiViDXQAITlxUxFkf1pCNVEpEwUhZztsI15sAmVWQCkEKnUxcRpDISgGPFcSd0wHd11lKGM1Pn80J0RbEhEvayU6GXYcUwQVSThRFWM6IQ4gLwRCG2wAHE59OgYYZFcOBlsQA3VWJQ%3D%3D; __c=1601602461; __l=l=%2Fwww.zhipin.com%2F&r=&g=&friend_source=0&friend_source=0; __a=80430348.1601602461..1601602461.7.1.7.7',
'Host': 'www.zhipin.com',
'Referer': 'https://www.zhipin.com/job_detail/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&city=100010000&industry=&position=',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0'
}
res = requests.get('https://www.zhipin.com/job_detail/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&city=100010000&industry=&position=', headers=header)
res.text
此时输出的信息更加完整。
同时还可以将请求到的内容保存到文件中,如下:
html_file = open('bosspage.html','w', encoding='utf-8')
html_file.write(res.text)
html_file.close()
运行后,可以看到当前目录下已经多了一个文件bosspage.html,也可以在浏览器中打开该文件,页面是和之前的页面一样的效果,所需要的信息也保存在HTML代码中。
有了网页代码之后,就可以提取信息了,之前是用字符串方式提取字符串的,现在选择BeautifulSoup来选择所需要的信息。
简单使用如下:
html = """
The Dormouse's title
The Dormouse's story
Once upon a time there were three little sisters; and their names were
,
Lacie and
Tillie;
and they lived at the bottom of a well.
...
"""
soup_first = bs(html, 'html.parser')
soup_first.prettify()
输出:
'\n \n \n The Dormouse\'s title\n \n \n \n \n \n The Dormouse\'s story\n \n
\n \n Once upon a time there were three little sisters; and their names were\n \n \n \n ,\n \n Lacie\n \n and\n \n Tillie\n \n ;\nand they lived at the bottom of a well.\n
\n \n ...\n
\n \n\n'
可以获取标签中所有的文本,如下:
soup_first.text
输出:
"\nThe Dormouse's title\n\nThe Dormouse's story\nOnce upon a time there were three little sisters; and their names were\n,\nLacie and\nTillie;\nand they lived at the bottom of a well.\n...\n\n\n"
也可以获取标签的属性,如下:
all_a = soup_first.find_all("a")
all_a[0]["href"]
输出:
'http://example.com/elsie'
可以看到,获取到了a标签的href属性,即链接。
还可以获取所有链接,如下:
[a['href'] for a in soup_first.find_all("a")]
输出:
['http://example.com/elsie',
'http://example.com/lacie',
'http://example.com/tillie']
还有其他一些用法:
display(soup_first.title,soup_first.head,soup_first.a,soup_first.p.string,soup_first.find_all("a"))
输出:
<title>The Dormouse's title</title>
<head><title>The Dormouse's title</title></head>
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
"The Dormouse's story"
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
使用BOSS直聘页面对BeautifulSoup进行初始化:
soup = bs(res.text, 'lxml')
soup.prettify()
定位到所需要的信息,如下:
all_jobs = soup.find_all("div", class_="job-primary")
all_jobs[0]
输出:
<div class="job-primary">
<div class="info-primary">
<div class="primary-wrapper">
<div class="primary-box" data-index="0" data-itemid="1" data-jid="7271f2f28169375a1nR42t-6GFpQ" data-jobid="102127880" data-lid="nlp-arJU8s0LBOW.search.1" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" ka="search_list_1" target="_blank">
<div class="job-title">
<span class="job-name"><a data-index="0" data-itemid="1" data-jid="7271f2f28169375a1nR42t-6GFpQ" data-jobid="102127880" data-lid="nlp-arJU8s0LBOW.search.1" href="/job_detail/7271f2f28169375a1nR42t-6GFpQ.html" ka="search_list_jname_1" target="_blank" title="数据分析">数据分析a>span>
<span class="job-area-wrapper">
<span class="job-area">北京·朝阳区·鸟巢span>
span>
<span class="job-pub-time">span>
div>
<div class="job-limit clearfix">
<span class="red">50-80K·14薪span>
<p>3-5年<em class="vline">em>本科p>
<div class="info-publis">
<h3 class="name"><img class="icon-chat" src="https://z.zhipin.com/web/geek/resource/icon-chat-v2.png"/>曹先生<em class="vline">em>数据挖掘h3>
div>
<button class="btn btn-startchat" data-url="/wapi/zpgeek/friend/add.json?jobId=7271f2f28169375a1nR42t-6GFpQ&lid=nlp-arJU8s0LBOW.search.1" href="javascript:;" redirect-url="/web/geek/chat?id=495f7159c0c8664a1nFz39m8EA~~">
<img alt="" class="icon-chat icon-chat-hover" src="https://z.zhipin.com/web/geek/resource/icon-chat-hover-v2.png"/>
<span>立即沟通span>
button>
div>
<div class="info-detail">div>
div>
div>
<div class="info-company">
<div class="company-text">
<h3 class="name"><a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage" target="_blank" title="京东集团招聘">京东集团a>h3>
<p><a class="false-link" href="/i100001/" ka="search_list_company_industry_1_custompage" target="_blank" title="电子商务行业招聘信息">电子商务a><em class="vline">em>已上市<em class="vline">em>10000人以上p>
div>
<a href="/gongsi/33e052361693f8371nF-3d25.html" ka="search_list_company_1_custompage_logo" target="_blank"><img alt="" class="company-logo" src="https://img.bosszhipin.com/beijin/mcs/bar/20191129/3cdf5ba2149e309b38868b62ae9c22cabe1bd4a3bd2a63f070bdbdada9aad826.jpg?x-oss-process=image/resize,w_100,limit_0"/>a>
div>
div>
<div class="info-append clearfix">
<div class="tags">
<span class="tag-item">Excelspan>
<span class="tag-item">SPSSspan>
<span class="tag-item">Pythonspan>
<span class="tag-item">数据挖掘span>
<span class="tag-item">数据仓库span>
div>
<div class="info-desc">补充医疗保险,节日福利,定期体检,年终奖,餐补,交通补助,免费班车,包吃,股票期权,员工旅游,零食下午茶,五险一金,带薪年假div>
div>
div>
可以看到,这是一条职位的详细信息。
先对一条职位信息进行提取:
base_boss_url ="https://www.zhipin.com"
job_link= base_boss_url + all_jobs[0].a["href"]
job_title = all_jobs[0].a.text
job_salary = all_jobs[0].find('span',class_='red').text
other_detail = all_jobs[0].find("div", class_="info-detail").text
company_url = base_boss_url + all_jobs[0].select(".info-company")[0].a["href"]
company = all_jobs[0].select(".info-company")[0].a.text
company_info = all_jobs[0].select(".info-company")[0].p.text
publish_info = all_jobs[0].find("div",class_="info-publis").h3.text
"{}-{}-{}-{}-{}-{}-{}-{}".format(job_link,job_title,job_salary,other_detail,company_url,company,company_info,publish_info)
输出:
'https://www.zhipin.com/job_detail/7271f2f28169375a1nR42t-6GFpQ.html-数据分析-50-80K·14薪--https://www.zhipin.com/gongsi/33e052361693f8371nF-3d25.html-京东集团-电子商务已上市10000人以上-曹先生数据挖掘'
显然,已经提取出1个职位的详情信息。
进一步通过for循环提取当前页中所有职位的信息,如下:
jobs_index = []
for job_ in all_jobs:
job_link= base_boss_url + job_.a["href"]
job_title = job_.a.text
job_salary = job_.find('span',class_='red').text
other_detail = job_.find("div", class_="info-detail").text
company_url = base_boss_url + job_.select(".info-company")[0].a["href"]
company = job_.select(".info-company")[0].a.text
company_info = job_.select(".info-company")[0].p.text
publish_info = job_.find("div",class_="info-publis").h3.text
jobs_index.append([job_link,job_title,job_salary,other_detail,company_url,company,company_info,publish_info])
jobs_index
输出:
[['https://www.zhipin.com/job_detail/7271f2f28169375a1nR42t-6GFpQ.html',
'数据分析',
'50-80K·14薪',
'',
'https://www.zhipin.com/gongsi/33e052361693f8371nF-3d25.html',
'京东集团',
'电子商务已上市10000人以上',
'曹先生数据挖掘'],
['https://www.zhipin.com/job_detail/1fe1d55e100e19d43nR509m-E1Q~.html',
'数据分析',
'18-35K·15薪',
'',
'https://www.zhipin.com/gongsi/918159f26789c3891nV53dQ~.html',
'小红书',
'互联网D轮及以上1000-9999人',
'刘先生商业数据中台'],
['https://www.zhipin.com/job_detail/4423d7c2eda602351nR-09u0EVs~.html',
'数据分析',
'25-40K·16薪',
'',
'https://www.zhipin.com/gongsi/fa2f92669c66eee31Hc~.html',
'BOSS直聘',
'人力资源服务D轮及以上1000-9999人',
'艾力凡先生数据分析'],
['https://www.zhipin.com/job_detail/9c2e41ed166d74bd03J-29u0F1s~.html',
'商业数据分析',
'25-40K·15薪',
'',
'https://www.zhipin.com/gongsi/980f48937a13792b1nd63d0~.html',
'滴滴出行',
'移动互联网D轮及以上1000-9999人',
'王先生商业分析高级经理'],
['https://www.zhipin.com/job_detail/27d069780b8cc5c53nV62dS8EFE~.html',
'数据分析岗',
'20-40K·14薪',
'',
'https://www.zhipin.com/gongsi/6e19637143bd80ad1HV_3N26GQ~~.html',
'建信金科',
'银行不需要融资1000-9999人',
'王先生架构师/研究员'],
...
['https://www.zhipin.com/job_detail/4c408eec4076e9d80nV73NW5FVU~.html',
'数据分析师',
'30-50K·16薪',
'',
'https://www.zhipin.com/gongsi/ea9c5680f57d53d71HV90ty5.html',
'拼多多',
'移动互联网已上市1000-9999人',
'王女士商业化部数据团队leader'],
['https://www.zhipin.com/job_detail/9f44d60c7097321033142tu4FVI~.html',
'业务数据分析',
'20-30K',
'',
'https://www.zhipin.com/gongsi/92674acda23901841nd_292-EQ~~.html',
'车好多集团',
'互联网D轮及以上10000人以上',
'李女士HR'],
['https://www.zhipin.com/job_detail/a6df576d9539ad810HN439i7Flo~.html',
'数据分析师',
'30-50K·14薪',
'',
'https://www.zhipin.com/gongsi/48e6b3630a48ccdb03N-2di9.html',
'分享动力',
'互联网不需要融资500-999人',
'陈女士招聘者'],
['https://www.zhipin.com/job_detail/89713a5a1647e44e0XF63dW8F1Y~.html',
'数据分析师',
'20-30K·13薪',
'',
'https://www.zhipin.com/gongsi/d6f0653b1a4d44740XB_29W0.html',
'猿辅导',
'在线教育D轮及以上1000-9999人',
'毛女士hrbp高级经理'],
['https://www.zhipin.com/job_detail/7585af83791f132833F639u7Flo~.html',
'数据分析师',
'15-25K',
'',
'https://www.zhipin.com/gongsi/f12428f4426b92a033V52tU~.html',
'360',
'移动互联网已上市1000-9999人',
'张女士HRBP']]
显然,此时提取出的都是有用的信息。
由于有多页,因此需要翻页获取每一页的信息,此时需要获取到页面中的下一页链接,如下:
next_page = base_boss_url + soup.find("a", class_="next")['href']
next_page
输出:
'https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=2'
显然,获取到了下一页的链接。
进一步用函数的形式实现:
def extract_jobs(page):
page_soup = bs(page, 'lxml')
all_jobs = page_soup.find_all("div", class_="job-primary")
jobs_index = []
print("parseing page ",page_soup.title.text)
for job_ in all_jobs:
job_link= base_boss_url + job_.a["href"]
job_title = job_.a.text
job_salary = job_.find('span',class_='red').text
other_detail = job_.find("div", class_="info-detail").text
company_url = base_boss_url + job_.select(".info-company")[0].a["href"]
company = job_.select(".info-company")[0].a.text
company_info = job_.select(".info-company")[0].p.text
publish_info = job_.find("div",class_="info-publis").h3.text
jobs_index.append([job_link,job_title,job_salary,other_detail,company_url,company,company_info,publish_info])
next_page = base_boss_url + soup.find("a", class_="next")['href']
print("next page is ",next_page)
return jobs_index, next_page
再循环实现爬取多页:
next_page = "https://www.zhipin.com/job_detail/?query=数据分析&city=100010000&industry=&position="
header = {
'Cookie': 'Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601622554; lastCity=100010000; __g=-; toUrl=https%3A%2F%2Fwww.zhipin.com%2Fc100010000%2F%3Fquery%3D%25E6%2595%25B0%25E6%258D%25AE%25E5%2588%2586%25E6%259E%2590%26page%3D2%26ka%3Dpage-2; t=CPzVdSehDMWYI0ch; wt=CPzVdSehDMWYI0ch; _bl_uid=70kkOfndrz2x09b2wqjXvwRw7CXh; __c=1601622556; __l=l=%2Fwww.zhipin.com%2Fjob_detail%2F%3Fquery%3D%25E6%2595%25B0%25E6%258D%25AE%25E5%2588%2586%25E6%259E%2590%26city%3D100010000%26industry%3D%26position%3D&r=&g=&friend_source=0&friend_source=0; __a=10559958.1598103978.1598103978.1601622556.20.2.19.20; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601625518; __zp_stoken__=cb83bGmgSGWkpKCwDKD94UGNacAUEGlI0IiUsTFEZOkpsdHcVUH9dZWN0U3hoOykGPFcSd0wHeyVlID01OwRMXh5NPCtDNBRnZXAZTAIVSThRFWM6IQ86BGZgXnpPRRhtOgYYZFcOBlsQA3VWJQ%3D%3D',
'Host': 'www.zhipin.com',
'Referer': 'https://www.zhipin.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
counter = 0
all_jobs = []
while next_page != "javascript:;":
print("start to fecth url ",next_page)
boss_response = requests.get(next_page, headers=header)
jobs, next_page = extract_jobs(boss_response.text)
counter +=1
if len(jobs) > 0:
all_jobs = all_jobs + jobs
if counter > 3:
break
time.sleep(random.randint(5,12))
输出如下:
start to fecth url https://www.zhipin.com/job_detail/?query=数据分析&city=100010000&industry=&position=
parseing page 「全国数据分析招聘」-2020年全国数据分析最新人才招聘信息 - BOSS直聘
start to fecth url https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=2
parseing page 「全国数据分析招聘」-2020年全国数据分析最新人才招聘信息 - BOSS直聘
start to fecth url https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=3
parseing page 「全国数据分析招聘」-2020年全国数据分析最新人才招聘信息 - BOSS直聘
start to fecth url https://www.zhipin.com/c100010000/?query=%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90&page=4
parseing page 「全国数据分析招聘」-2020年全国数据分析最新人才招聘信息 - BOSS直聘
由于现在BOSS直聘的反爬措施比较严厉,因此必须在请求头中加入Cookie信息(这是验证一个用户的基本信息),在浏览器中Cookie的获取方法如下:
注意,需要获取第一条网络请求(即类似于https://www.zhipin.com/job_detail/?query=数据分析&city=100010000&industry=&position=的请求)对应的Cookie,因为不同的请求对应的Cookie可能会有所不同;
并且,一个Cookie一般只能用一次,因此爬取一次应该重新获取Cookie,最好是注册用户之后再获取Cookie、这样更有效。
同时为了控制访问频率,每执行一次翻页循环后,都通过time.sleep()
方法暂停执行。
此时查看获取到的数据,如下:
all_jobs
输出:
[['https://www.zhipin.com/job_detail/e1bde0976de53e081nR43dm5EFRU.html',
'数据分析师(实习)',
'200-300元/天',
'',
'https://www.zhipin.com/gongsi/2e64a887a110ea9f1nRz.html',
'腾讯',
'互联网已上市10000人以上',
'黄女士HRBP'],
['https://www.zhipin.com/job_detail/062a0a30e8b663103nJy2N65E1E~.html',
'【校招】数据分析师',
'20-30K·16薪',
'',
'https://www.zhipin.com/gongsi/fa2f92669c66eee31Hc~.html',
'BOSS直聘',
'人力资源服务D轮及以上1000-9999人',
'BOSS直聘校招校园招聘'],
['https://www.zhipin.com/job_detail/5b132f8291af536d3nN42di7F1Y~.html',
'周末双休 数据分析',
'7-12K',
'',
'https://www.zhipin.com/gongsi/aa07960c21a559c61nV_3N24GFs~.html',
'北京磐程',
'电子商务100-499人',
'王女士人事经理'],
...
['https://www.zhipin.com/job_detail/3bcf1023eea94e363nN_3d65GFM~.html',
'数据分析师',
'7-8K',
'',
'https://www.zhipin.com/gongsi/c58313ff6a0317b10HN83d-0.html',
'坚果动力',
'游戏A轮100-499人',
'李强制作人'],
['https://www.zhipin.com/job_detail/0cf98b59a339fd4603R90tS5FlQ~.html',
'数据分析师',
'8-10K',
'',
'https://www.zhipin.com/gongsi/90ffbb07580a82d203d73d-5Fw~~.html',
'北京立言创新科技...',
'学术/科研未融资0-20人',
'高晓玲设计师']]
显然,已经获取到了需要的数据。
还可以进一步保存到文件中,如下:
fout = open('job_data.csv', 'wt')
for info in all_jobs:
fout.write(",".join(info)+"\n")
fout.close()
执行成功后,列表中会多出一个文件job_data.csv。
获取职位详情时,可以利用之前获取到的详情链接,通过requests模拟请求并使用BeautifulSoup解析。
先以一个商品详情链接为例进行探究。
查看网页如下:
可以看到,职位详情都在class为detail-content的div中。
获取一个职位详情页的详情信息,如下:
detail_link = all_jobs[0][0]
header = {
'Cookie': 'lastCity=100010000; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601602464,1601624966; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601627370; __zp_stoken__=cb83bGmgSGWkpKFVye2gnUGNacAVQeH5ZeQEsTFEZOiALeWBKTX9dZWN0eHZBaRkGPFcSd0wHey9kCTc1M2kdDjAjby9CXQRiHX9yWnsLSThRFWM6IT9oLWhLXnpPRRhwOgYYZFcOBlsQA3VWJQ%3D%3D; __fid=7627d554a7f83f762fe906cbda0d7906; __g=-; __c=1601602461; __l=l=%2Fwww.zhipin.com%2Fc100010000%2F%3Fquery%3D%25E6%2595%25B0%25E6%258D%25AE%25E5%2588%2586%25E6%259E%2590%26page%3D5&r=http%3A%2F%2F127.0.0.1%3A8888%2Fnotebooks%2Fcrawl_boss.ipynb&g=&friend_source=0&friend_source=0; __a=80430348.1601602461..1601602461.23.1.23.23',
'Host': 'www.zhipin.com',
'Referer': 'https://www.zhipin.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
job_detail = requests.request("GET", detail_link,headers=header)
job_soup = bs(job_detail.text,"lxml")
detail_text = job_soup.find("div",class_="job-sec")
detail_text.text
输出:
'\n职位描述\n\n 【腾讯视频号】数据分析-日常实习生【仅接受985/211或海外大学对口专业简历】岗位职责负责微信视频号产品的各项数据分析相关工作岗位要求1. 数据科学/统计/数学专业/信管/计算机等相关专业本科及以上学历;2. 良好的数据结构和算法基础,优秀的编码能力;3.数据驱动,熟练使用sql、excel,能高效利用数据指导并优化方案,Python 或 R(必须),SQL(必须),Tableau(加分,建议)Excel(必须)具有海量数据处理经验;4. 具有良好的沟通能力、坦诚直接、重视团队合作;5.有才艺其他兴趣爱好,互联网公司同样实习经历,自我经营账号的优先6. 一周实习4天以上,能够立即到岗,实习3个月以上7. 必须是有学籍的在校生,优先考虑2021届和2021届以后毕业同学。其他:1.地点:北京线下;2.待遇:高额薪水,差旅交通报销,免费三餐,大空间,团队氛围nice\n \n'
对文本进行进一步的处理:
detail_text = detail_text.text.replace("\n","").replace(" ","")
detail_text
输出:
'职位描述【腾讯视频号】数据分析-日常实习生【仅接受985/211或海外大学对口专业简历】岗位职责负责微信视频号产品的各项数据分析相关工作岗位要求1.数据科学/统计/数学专业/信管/计算机等相关专业本科及以上学历;2.良好的数据结构和算法基础,优秀的编码能力;3.数据驱动,熟练使用sql、excel,能高效利用数据指导并优化方案,Python或R(必须),SQL(必须),Tableau(加分,建议)Excel(必须)具有海量数据处理经验;4.具有良好的沟通能力、坦诚直接、重视团队合作;5.有才艺其他兴趣爱好,互联网公司同样实习经历,自我经营账号的优先6.一周实习4天以上,能够立即到岗,实习3个月以上7.必须是有学籍的在校生,优先考虑2021届和2021届以后毕业同学。其他:1.地点:北京线下;2.待遇:高额薪水,差旅交通报销,免费三餐,大空间,团队氛围nice'
显然,页面美观了很多。
进一步通过循环获取多个详情链接的详情信息:
job_desc=[]
header = {
'Cookie': 'Cookie: lastCity=100010000; Hm_lvt_194df3105ad7148dcf2b98a91b5e727a=1601602464,1601624966; Hm_lpvt_194df3105ad7148dcf2b98a91b5e727a=1601628313; __zp_stoken__=cb83bGmgSGWkpKF9eQW0WUGNacAVVDB9sNDssTFEZOlIDHXcKU39dZWN0enMzK2IGPFcSd0wHeyAzIGM1LHd1KFU0Y1BHPxZtbHF0XH4cSThRFWM6IUQqX21JXnpPRRhuOgYYZFcOBlsQA3VWJQ%3D%3D; __fid=7627d554a7f83f762fe906cbda0d7906; __g=-; ___gtid=729532789; __c=1601602461; __l=l=%2Fwww.zhipin.com%2Fjob_detail%2F7271f2f28169375a1nR42t-6GFpQ.html%3Fka%3Dsearch_list_jname_1_blank%26lid%3Dnlp-axWMPTPcuB6.search.1&r=http%3A%2F%2F127.0.0.1%3A8888%2Fnotebooks%2Fcrawl_boss.ipynb&g=&friend_source=0&friend_source=0; __a=80430348.1601602461..1601602461.28.1.28.28',
'Host': 'www.zhipin.com',
'Referer': 'https://www.zhipin.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36',
}
for job in all_jobs[:4]:
print(".",end="")
job_detail = requests.request("GET", job[0], headers=header)
job_soup = bs(job_detail.text,"lxml")
detail_text = job_soup.find("div",class_="job-sec").text.replace("\n","").replace(" ","")
job_desc.append([job[0],detail_text])
time.sleep(random.random()*3)
job_desc
输出:
[['https://www.zhipin.com/job_detail/e1bde0976de53e081nR43dm5EFRU.html',
'职位描述【腾讯视频号】数据分析-日常实习生【仅接受985/211或海外大学对口专业简历】岗位职责负责微信视频号产品的各项数据分析相关工作岗位要求1.数据科学/统计/数学专业/信管/计算机等相关专业本科及以上学历;2.良好的数据结构和算法基础,优秀的编码能力;3.数据驱动,熟练使用sql、excel,能高效利用数据指导并优化方案,Python或R(必须),SQL(必须),Tableau(加分,建议)Excel(必须)具有海量数据处理经验;4.具有良好的沟通能力、坦诚直接、重视团队合作;5.有才艺其他兴趣爱好,互联网公司同样实习经历,自我经营账号的优先6.一周实习4天以上,能够立即到岗,实习3个月以上7.必须是有学籍的在校生,优先考虑2021届和2021届以后毕业同学。其他:1.地点:北京线下;2.待遇:高额薪水,差旅交通报销,免费三餐,大空间,团队氛围nice'],
['https://www.zhipin.com/job_detail/062a0a30e8b663103nJy2N65E1E~.html',
'职位描述我们的日常工作:1、撰写code(包括SQL/Shell/Python/R等),提取、加工相关的业务数据2、应用Excel/Python以及一些可视化工具进行数据可视化的相关分析3、撰写分析报告,给出相关的分析结论。主要分为几个方面:产品业务优化迭代的效果评估,业务假设的数据验证,业务优化的实施策略的制定,业务可能存在的问题的研究定位,业务发展的战略方向探索4、与产品、市场、运营、销售、设计等各个部门进行业务分析结论的沟通,使得数据分析的发现能够驱动业务的相关优化我们眼中的你:1、这个岗位需要撰写大量的代码,所以希望你不是一个害怕跟代码打交道的人。2、这个岗位需要进行数据分析,会需要掌握很多数学相关的知识,所以希望你不是一个从小不喜欢学习数学的人。3、这个岗位涉及大量的与人沟通的工作,所以希望你不是一个过于腼腆的人。4、这个岗位的目的是通过数据分析以理解用户的行为的内在机理,所以我们希望你是有较好的同理心的人,平时是乐于去理解、并能够照顾别人感受的人。5、为了能够更好的理解业务,我们会需要进行广泛地学习,如经济学、心理学、系统论、信息论等等,所以希望你是一个能够不断挑战自己,对所有的未知都抱有足够的好奇心的人。6、知识可以学习、能力可以锻炼、心智可以培养,最终还是需要你是一个有足够的事业追求的人。'],
['https://www.zhipin.com/job_detail/5b132f8291af536d3nN42di7F1Y~.html',
'职位描述技能要求:数据分析,数据仓库1、搜集行业相关信息,为相关需求者提供更准确的数据信息;2、丰富市场分析能力,做出每日分析计划,熟练掌握各种分析技术;3、对市场、行业、公司运营等提供数据分析计划,为战略决策提供支持;4、发表研究成果或分析评论,配合公司的推广及培训等工作。5、协助部门经理完善部门管理制度;'],
['https://www.zhipin.com/job_detail/b8f8a877b20685010XF62Nm1FVs~.html',
'职位描述【岗位职责】\u20281、参与原始数据、数据抽取、治理、统计分析到报表展示的全部流程2、深入理解业务,发现业务特征,进行衍生数据价值挖掘\u2028【任职条件】1、计算机等相关专业者优先;2、熟悉SQL,有使用HIVESQL或者SPARKSQL经验者,熟练使用java,python或者scala优先;3、思路开阔且灵活,对数字敏感,善于从数据中发现问题并抓住重点;4,具备良好的数据敏感度、良好的逻辑思维,能及时发现和分析数据中隐含的变化和问题;5、良好的逻辑思维能力,能够从海量数据中发现有价值的规律6、了解spark生态,有大数据处理经验的优先7、实习期至少6个月。']]
进一步保存数据如下:
fout = open('job_desc.csv', 'wt', encoding='gbk')
for info in job_desc:
fout.write("{},\"{}\"\n".format(info[0],info[1].encode('gbk', 'ignore').decode('gbk', 'ignore')))
fout.close()
再查看当前目录,多了文件为job_desc.csv。
对于中文详情的描述,需要先进行分词,即将文段分成较短的词语,使用jieba库分词,使用前需要先通过命令conda install -c conda-forge jieba
进行安装。
jieba库的简单使用如下:
import jieba
fenci = jieba.cut("我在北京上大学,我上的是比清华好的北京大学",cut_all=True)
'/'.join(fenci)
输出:
'我/在/北京/上/大学///我/上/的/是/比/清华/好/的/北京/北京大学/大学'
进一步使用如下:
fenci = jieba.cut("我在北京读大学,我读的是比清华好的北京大学",cut_all=False)
print("/ ".join(fenci))
fenci = jieba.cut("我爱北京天安门,五环比六环少一环,学好python就不是低端劳动力了,呜呜",cut_all=False)
print("/ ".join(fenci))
jieba.suggest_freq("六环", tune=True)
fenci = jieba.cut("我爱北京天安门,五环比六环多一环,学好python就不是低端劳动力了,呜呜",cut_all=False)
print("/ ".join(fenci))
输出:
我/ 在/ 北京/ 读/ 大学/ ,/ 我读/ 的/ 是/ 比/ 清华/ 好/ 的/ 北京大学
我/ 爱/ 北京/ 天安门/ ,/ 五环/ 比六环少/ 一环/ ,/ 学好/ python/ 就/ 不是/ 低端/ 劳动力/ 了/ ,/ 呜呜
我/ 爱/ 北京/ 天安门/ ,/ 五环/ 比/ 六环/ 多一环/ ,/ 学好/ python/ 就/ 不是/ 低端/ 劳动力/ 了/ ,/ 呜呜
对job_desc进行处理如下:
combined_job_desc = " ".join([j[1] for j in job_desc])
fenci_job_desc = jieba.cut(combined_job_desc,cut_all=False)
space = " ".join(fenci_job_desc)
space
在使用词云之前,需要通过命令conda install -c conda-forge wordcloud
安装wordcloud库。
生成词云对象如下:
# 生成WordCloud对象
wc = WordCloud(
# width=800,
# height=600,
background_color="white", # 设置背景颜色
max_words=200, # 词的最大数(默认为200)
colormap='viridis', # string or matplotlib colormap, default="viridis"
random_state=10, # 设置有多少种随机生成状态,即有多少种配色方案
font_path='STLITI.TTF' # 设置字体路径
)
##comments
my_wordcloud = wc.generate(space)
注意:
再进行初始化时,如果是中文词,需要指定font_path,即字体路径,并且需要在路径下有对应的字体文件。
如需获取字体文件进行测试,可以直接点击加QQ群 963624318 ,在群文件夹商业数据分析从入门到入职中下载即可,Windows系统也可以在C:\Windows\Fonts中选择支持中文的字体复制到项目路径下。
展示词云:
import matplotlib.pyplot as plt
%matplotlib inline
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.figure()
可以看到,根据出现关键字的次数权重而区分词语的大小,形成有区分度的词云统计。
但是还可以进一步优化,去掉一些重复的、没有意义的词语,可以在初始化WordCloud对象时使用stopwords
参数忽略掉这些词。
如下:
wc = WordCloud(
# width=800,
# height=600,
background_color="white", # 设置背景颜色
# max_words=200, # 词的最大数(默认为200)
colormap='viridis', # string or matplotlib colormap, default="viridis"
random_state=10, # 设置有多少种随机生成状态,即有多少种配色方案
font_path='STKAITI.TTF',
stopwords=('数据','数据分析','职位描述','工作','工作内容','职责','工作职责','任职要求','职位','描述','产品','经验','熟练',
'进行','运营','相关','以上学历','使用','工具','本科','提供','负责','业务','熟悉','分析','优先','能力','策略',
'任职','熟悉','开发','项目','公司','需求','支持','岗位职责','行业','问题','研究','逻辑','具有','搭建','能够',
'决策','完成','技术','监控','客户','基于','方法','设计','了解','良好','部门','日常','通过','团队','互联网','根据'
,'建立','以及','具备','发现','应用','业务部门','制定','掌握','要求','平台','基础','以上','推动','体系','管理'
,'较强','学习','管理','资格','建议','专业','落地','协助','执行','价值','方案','提出','解决','快速','优秀','参与',
'方向','改进','建设','评估','研发','信息','提取','深入','常用','包括','岗位','理解','用户')
)
my_wordcloud = wc.generate(space)
plt.imshow(wc, interpolation="bilinear")
plt.axis("off")
plt.figure()
可以看到,相对于之前,有更好的说服力。
还可以进一步统计词频,如下:
from jieba import analyse
keywords = analyse.extract_tags(combined_job_desc, topK=300, withWeight=True, allowPOS=('n',))
keywords
输出:
[('数据', 0.3934027646776582),
('业务', 0.35639458308689875),
('岗位', 0.19911889163164556),
('职位', 0.19509550027518988),
('能力', 0.1561817429800633),
...
('全部', 0.03118962121886076),
('条件', 0.030984291967974684),
('基础', 0.030147029148860763),
('技术', 0.0298699821428481),
('方面', 0.026981713165253163)]
因为很多技能都是用英文表示的,如MySQL、Python等,因此可以进一步去掉中文,再进行分析。
示意如下:
import re
s = 'hi新手oh'
remove_chinese = re.compile(r'[\u4e00-\u9fa5]') #[\u4e00-\u9fa5]是匹配所有中文的正则表达式
remove_chinese.split(s)
输出:
['hi', '', 'oh']
可以看到,去掉了字符串中的中文。
对职位要求详情去中文如下:
all_english = ''.join(remove_chinese.split(combined_job_desc))
all_english
输出:
'【】-【985/211】1.;2.,;3.,sql、excel,,PythonR(),SQL(),Tableau(,)Excel();4.、、;5.,,6.4,,37.,20212021。:1.:;2.:,,,,nice :1、code(SQL/Shell/Python/R),、2、Excel/Python3、,。:,,,,4、、、、、,:1、,。2、,,。3、,。4、,,、。5、,,、、、,,。6、、、,。 :,1、,;2、,,;3、、、,;4、,。5、; 【】\u20281、、、、2、,,\u2028【】1、;2、SQL,HIVESQLSPARKSQL,java,pythonscala;3、,,;4,、,;5、,6、spark,7、6。'
同时,很多英文因为大小写等原因,其实也是表达的同一个意思,如SQL
和sql
,意思一样,只是大小写不同,可以合并统计:
combined_job_desc.count("SQL") + combined_job_desc.count("sql")
输出:
6
分析词频如下:
keywords = jieba.analyse.extract_tags(all_english, topK=300, withWeight=True, allowPOS=())
keywords
输出:
[('SQL', 1.5593175003782607),
('Excel', 1.0395450002521738),
('985', 0.5197725001260869),
('211', 0.5197725001260869),
('sql', 0.5197725001260869),
('excel', 0.5197725001260869),
('PythonR', 0.5197725001260869),
('Tableau', 0.5197725001260869),
('6.4', 0.5197725001260869),
('37', 0.5197725001260869),
('20212021', 0.5197725001260869),
('nice', 0.5197725001260869),
('code', 0.5197725001260869),
('Shell', 0.5197725001260869),
('Python', 0.5197725001260869),
('Python3', 0.5197725001260869),
('HIVESQLSPARKSQL', 0.5197725001260869),
('java', 0.5197725001260869),
('pythonscala', 0.5197725001260869),
('spark', 0.5197725001260869)]
此时,词频有很大变化。
再画词云如下:
eng_job_desc = jieba.cut(all_english,cut_all=False)
en_space = " ".join(eng_job_desc)
wc_eng = WordCloud(
# width=1600,
# height=800,
background_color="white", # 设置背景颜色
max_words=300, # 词的最大数(默认为200)
colormap='viridis', # string or matplotlib colormap, default="viridis"
random_state=10, # 设置有多少种随机生成状态,即有多少种配色方案
# font_path='./fonts/cn/msyh.ttc'
)
##comments
my_wordcloud = wc_eng.generate(en_space)
plt.imshow(wc_eng, interpolation="bilinear")
plt.axis("off")
plt.figure()
王者荣耀英雄列表网页为https://pvp.qq.com/web201605/herolist.shtml,展示了英雄的基本信息。
前面是从网页中大量数据中找出有用的信息,但是对于有的网站来说还有更简单的方式,如有的网站提供了数据API,即通过JSON形式提供数据到前端再渲染显示,显然,直接从JSON API中获取数据更简单高效。
如王者荣耀英雄列表网页就使用了JSON数据,如下:
可以看到,其地址为https://pvp.qq.com/web201605/js/herolist.json,包含了所有英雄的基本信息,可以下载该JSON文件,然后就可以直接从文件中获取信息,而不需要再从网页中解析了,并将这些信息与网页中的信息进行整合、形成更加完善的信息,并实现可以通过关键字查询相关英雄的信息。
先导入所需要的库并获取到JSON数据,如下:
import json
import requests
from bs4 import BeautifulSoup as bs
rongyao_response = requests.request("GET", "https://pvp.qq.com/web201605/js/herolist.json")
rongyao_response.text
将其保存到本地文件,如下:
r = requests.get('https://pvp.qq.com/web201605/js/herolist.json', stream=True)
with open("herolist.json", 'wb') as fd:
for chunk in r.iter_content(chunk_size=128):
fd.write(chunk)
对JSON对象的操作可以有json库实现。
将JSON对象转化为字典如下:
json_obj = """
{ "zoo_animal": "Lion",
"food": ["Meat", "Veggies", "Honey"],
"fur": "Golden",
"clothes": null,
"diet": [{"zoo_animal": "Gazelle", "food":"grass", "fur": "Brown"}]
}
"""
data = json.loads(json_obj)
data
输出:
{
'zoo_animal': 'Lion',
'food': ['Meat', 'Veggies', 'Honey'],
'fur': 'Golden',
'clothes': None,
'diet': [{
'zoo_animal': 'Gazelle', 'food': 'grass', 'fur': 'Brown'}]}
也可以将字典转化为JSON对象,如下:
json.dumps(data)
输出:
'{"zoo_animal": "Lion", "food": ["Meat", "Veggies", "Honey"], "fur": "Golden", "clothes": null, "diet": [{"zoo_animal": "Gazelle", "food": "grass", "fur": "Brown"}]}'
也可以读取JSON文件转化为字典,如下:
hero_list = None
with open('herolist.json','rb') as json_data:
hero_list = json.load(json_data)
print(hero_list[:5])
输出:
[{
'ename': 105, 'cname': '廉颇', 'title': '正义爆轰', 'new_type': 0, 'hero_type': 3, 'skin_name': '正义爆轰|地狱岩魂'}, {
'ename': 106, 'cname': '小乔', 'title': '恋之微风', 'new_type': 0, 'hero_type': 2, 'skin_name': '恋之微风|万圣前夜|天鹅之梦|纯白花嫁|缤纷独角兽'}, {
'ename': 107, 'cname': '赵云', 'title': '苍天翔龙', 'new_type': 0, 'hero_type': 1, 'hero_type2': 4, 'skin_name': '苍天翔龙|忍●炎影|未来纪元|皇家上将|嘻哈天王|白执事|引擎之心'}, {
'ename': 108, 'cname': '墨子', 'title': '和平守望', 'new_type': 0, 'hero_type': 2, 'hero_type2': 1, 'skin_name': '和平守望|金属风暴|龙骑士|进击墨子号'}, {
'ename': 109, 'cname': '妲己', 'title': '魅力之狐', 'pay_type': 11, 'new_type': 0, 'hero_type': 2, 'skin_name': '魅惑之狐|女仆咖啡|魅力维加斯|仙境爱丽丝|少女阿狸|热情桑巴'}]
打印了前5个文件的信息。
获取每个英雄的类型,如下:
hero_type = ["全部","战士","法师","坦克","刺客","射手","辅助"]
for hero in hero_list:
combine_type = []
if "hero_type" in hero:
combine_type.append(hero_type[hero["hero_type"]])
if "new_type" in hero:
combine_type.append(hero_type[hero["new_type"]])
if "hero_type2" in hero:
combine_type.append(hero_type[hero["hero_type2"]])
print(hero["cname"] +" "+('|').join(combine_type))
输出:
廉颇 坦克|全部
小乔 法师|全部
赵云 战士|全部|刺客
墨子 法师|全部|战士
妲己 法师|全部
...
蒙犽 射手|全部
镜 刺客|全部
蒙恬 战士|全部
阿古朵 坦克|全部
夏洛特 战士|战士
此时再获取https://pvp.qq.com/web201605/herolist.shtml中的信息,包括图片链接等。
尝试如下:
html_hero_response = requests.request("GET", "https://pvp.qq.com/web201605/herolist.shtml")
html_hero_response.content.decode('gbk')
从输出中可以看到,输出中的英雄列表并不完整,与网页中实际现实的不一致,这可能是因为一部分信息是通过JavaScript等方式渲染到网页中的,网页源代码中没有,因此也未请求到。
此时可以使用selenium库来模拟访问浏览器,像人为一样操作浏览器,进而获取到英雄完整列表。
在使用前需要安装selenium库,直接通过conda install -c conda-forge selenium
命令即可安装;
还需要下载驱动,Chrome和FIrefox驱动均可,以Chrome为例,在下载前需要下载Chrome浏览器的版本,方式如下:
获取到版本后,再到http://chromedriver.storage.googleapis.com/index.html中选择与Chrome版本相近的驱动版本,如83.0.4103.14,点击后在当前版本下选择chromedriver_win32.zip
下载,下载解压后获取到chromedriver.exe文件,将其移动到Anaconda安装目录下的Scripts目录下,如E:\Anaconda3\Scripts
,如果不是使用的Anaconda,而是普通的Python环境,则移动到Python安装目录下的Scripts目录下,如E:\Python\Python38-32\Scripts
目录下,此时就可以使用selenium进行模拟访问了。
由于官网下载很缓慢,因此我已经将Chrome83.0.4103.14版本对应的驱动下载整理好了,可以直接点击加QQ群 963624318 在群文件夹Python相关安装包中下载即可,如需其他版本也可以在群里向群主提出。
模拟访问如下:
from selenium import webdriver
browser = webdriver.Chrome()
browser.get("https://pvp.qq.com/web201605/herolist.shtml")
html = browser.page_source
browser.quit()
执行,如下:
可以看到,有一个Chrome浏览器弹出并访问网站,获取到信息后自动关闭。
现在使用BeautifulSoup进行解析,获取英雄列表:
hero_soup = bs(html,'lxml')
hero_html_list=hero_soup.find("ul",class_="herolist")
all_hero_list =hero_html_list.find_all("li")
print(all_hero_list[0].text)
print("https://"+all_hero_list[0].img["src"].strip("/"))
输出:
夏洛特
https://game.gtimg.cn/images/yxzj/img201606/heroimg/536/536.jpg
显然,获取到了基本信息。
进一步整合,获取所有英雄名称和图片链接列表:
gen_heros=[[info.text, "https://"+info.img["src"].strip("/")] for info in all_hero_list]
gen_heros
输出L:
[['夏洛特', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/536/536.jpg'],
['阿古朵', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/533/533.jpg'],
['蒙恬', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/527/527.jpg'],
['镜', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/531/531.jpg'],
['蒙犽', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/524/524.jpg'],
...
['妲己', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/109/109.jpg'],
['墨子', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/108/108.jpg'],
['赵云', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/107/107.jpg'],
['小乔', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/106/106.jpg'],
['廉颇', 'https://game.gtimg.cn/images/yxzj/img201606/heroimg/105/105.jpg']]
现在需要将hero_list和gen_heros两个列表中的数据进行整合,并且实现根据关键字检索。
先实现给hero_list中的英雄定义英雄类型和根据英雄名在hero_list检索是否存在的函数,如下:
def build_hero_type(hero):
combine_type = []
if "hero_type" in hero:
combine_type.append(hero_type[hero["hero_type"]])
if "new_type" in hero:
combine_type.append(hero_type[hero["new_type"]])
if "hero_type2" in hero:
combine_type.append(hero_type[hero["hero_type2"]])
return(('|').join(combine_type))
def search_for_hero_info(name=None):
for hero in hero_list:
if "cname" in hero:
if hero["cname"] == name:
return hero
return None
这两个函数的简单使用如下:
su_lie=search_for_hero_info("苏烈")
print(su_lie)
hero_detail = search_for_hero_info(gen_heros[0][0])
print(hero_detail)
hero_detail["skin_name"].strip("
'")
build_hero_type(hero_detail)
输出如下:
{
'ename': 194, 'cname': '苏烈', 'title': '不屈铁壁', 'pay_type': 10, 'new_type': 0, 'hero_type': 3, 'hero_type2': 1, 'skin_name': '不屈铁壁|爱与和平|坚韧之力|玄武志'}
{
'ename': 536, 'cname': '夏洛特', 'title': '玫瑰剑士', 'new_type': 1, 'hero_type': 1, 'skin_name': '玫瑰剑士'}
'战士|战士'
现实现合并两个列表的函数:
def merge_hero_info(hero_html, hero_json):
all_heros = []
for hero in hero_html:
hero_detail = search_for_hero_info(hero[0])
all_heros.append([hero[0],build_hero_type(hero_detail),hero_detail.get("skin_name",'').strip("
'"),hero[1]])
return all_heros
使用该函数合并两个列表如下:
combined_heros=[]
combined_heros = merge_hero_info(gen_heros, hero_list)
combined_heros[:5]
输出:
[['夏洛特',
'战士|战士',
'玫瑰剑士',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/536/536.jpg'],
['阿古朵',
'坦克|全部',
'山林之子',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/533/533.jpg'],
['蒙恬',
'战士|全部',
'秩序统将|秩序猎龙将',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/527/527.jpg'],
['镜',
'刺客|全部',
'破镜之刃|冰刃幻境',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/531/531.jpg'],
['蒙犽',
'射手|全部',
'烈炮小子|归虚梦演',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/524/524.jpg']]
现在进一步实现建立索引实现快速查找,实现根据英雄名、英雄类型、英雄皮肤。
要实现输入一个keyword为苏烈,可以返回下面这样的结果:
['苏烈',
[['苏烈',
'坦克|战士|战士',
'不屈铁壁|爱与和平',
'http://game.gtimg.cn/images/yxzj/img201606/heroimg/194/194.jpg']]],
['坦克',
[['苏烈',
'坦克|战士|战士',
'不屈铁壁|爱与和平',
'http://game.gtimg.cn/images/yxzj/img201606/heroimg/194/194.jpg'],
['铠',
'战士|全部|坦克',
'破灭刃锋|龙域领主',
'http://game.gtimg.cn/images/yxzj/img201606/heroimg/193/193.jpg']
]
]
]
]
先实现根据英雄信息生成关键字列表:
# 根据英雄信息,生成keyword的列表
def get_keywords_array(hero):
keywords =[]
if hero[0]:
keywords.append(hero[0])
if hero[1]:
keywords += hero[1].split('|')
if hero[2]:
keywords +=hero[2].split('|')
return keywords
get_keywords_array(combined_heros[12])
输出:
['猪八戒', '坦克', '全部', '无忧猛士', '年年有余']
再实现添加索引和创建搜索列表的函数:
# 添加索引到搜索数据列表中
def add_to_index(index, keyword, info):
for entry in index:
if entry[0] == keyword:
entry[1].append(info)
return
#not find
index.append([keyword,[info]])
# 创建搜索数据列表
def build_up_index(index_array):
for hero_info in combined_heros:
keywords = get_keywords_array(hero_info)
for key in keywords:
add_to_index(index_array,key,hero_info)
最后实现根据关键字检索信息的函数:
# 根据关键词在列表中搜索
def lookup(index,keyword):
for entry in index:
if entry[0] == keyword:
return entry[1]
#not find
return entry[0]
检索测试如下:
search_index=[]
build_up_index(search_index)
lookup(search_index,"刺客")
输出:
[['镜',
'刺客|全部',
'破镜之刃|冰刃幻境',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/531/531.jpg'],
['马超',
'战士|全部|刺客',
'',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/518/518.jpg'],
['云中君',
'刺客|全部|战士',
'荷鲁斯之眼',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/506/506.jpg'],
['上官婉儿',
'法师|全部|刺客',
'惊鸿之笔|修竹墨客',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/513/513.jpg'],
['司马懿',
'刺客|全部|法师',
'寂灭之心|魇语军师',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/137/137.jpg'],
...
['韩信',
'刺客|全部',
'国士无双|街头霸王|教廷特使|白龙吟|逐梦之影',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/150/150.jpg'],
['貂蝉',
'法师|全部|刺客',
'绝世舞姬|异域舞娘|圣诞恋歌|逐梦之音|仲夏夜之梦',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/141/141.jpg'],
['李白',
'刺客|全部',
'青莲剑仙|范海辛|千年之狐|凤求凰|敏锐之力',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/131/131.jpg'],
['阿轲',
'刺客|全部',
'信念之刃|爱心护理|暗夜猫娘|致命风华|节奏热浪',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/116/116.jpg'],
['赵云',
'战士|全部|刺客',
'苍天翔龙|忍●炎影|未来纪元|皇家上将|嘻哈天王|白执事|引擎之心',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/107/107.jpg']]
可以看到,在英雄名、英雄类型和皮肤为刺客的数据都被检索出来。
此时再查看建立索引后的数据结构:
display(len(search_index),search_index[4])
输出:
446
['坦克',
[['阿古朵',
'坦克|全部',
'山林之子',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/533/533.jpg'],
['猪八戒',
'坦克|全部',
'无忧猛士|年年有余',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/511/511.jpg'],
['嫦娥',
'法师|全部|坦克',
'寒月公主|露花倒影',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/515/515.jpg'],
['孙策',
'坦克|全部|战士',
'光明之海|海之征途|猫狗日记',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/510/510.jpg'],
['梦奇',
'坦克|全部',
'入梦之灵|美梦成真|胖达荣荣',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/198/198.jpg'],
...
['白起',
'坦克|全部',
'最终兵器|白色死神|狰|星夜王子',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/120/120.jpg'],
['钟无艳',
'战士|全部|坦克',
'野蛮之锤|生化警戒|王者之锤|海滩丽影',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/117/117.jpg'],
['刘禅',
'辅助|全部|坦克',
'暴走机关|英喵野望|绅士熊喵|天才门将',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/114/114.jpg'],
['庄周',
'辅助|全部|坦克',
'逍遥幻梦|鲤鱼之梦|蜃楼王|云端筑梦师',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/113/113.jpg'],
['廉颇',
'坦克|全部',
'正义爆轰|地狱岩魂',
'https://game.gtimg.cn/images/yxzj/img201606/heroimg/105/105.jpg']]]
可以看到,为所有关键字都建立了索引,因此长度比英雄数量要多得多。
爬虫是Python最广泛的应用之一,可以从网页中快速获取大量数据。Python为我们提供了大量获取网络数据、提取网络数据和处理网络数据的库,如requests、selenium、BeautifulSoup、re、jieba、wordcloud等,合理灵活使用这些工具可以进行高效的爬虫开发。