目录
1.3 关于数据采集量过小以及可能存在的数据冗余和数据丢失问题的说明
2.2.3 getCountryEmployeeInfo(CountryEntry)
2.2.4 detailedInformation(detailedDescriptionHtml)
4.3.1 各地区招聘信息数量与年薪最大值平均值和最小值平均值的关系(剔除年薪面议情况)
4.4.1 抓取的招聘信息数量在全国各地的分布(剔除了国外的招聘信息)
4.5.1 所抓取的IT类招聘信息最喜欢用来吸引人的福利关键词
一、前言
1.1 关于我的水平以及该爬虫实现难度和代码中的小bug
这个关于网络爬虫还有数据可视化分析的小project是2019.1.19号左右开始写的,用了不到10天的时间完成,而我只是个大三学生,原来是一直用C++的,Python是2019.1.10号左右接触(这个时候我们正是考试周),所以从接触python到用python写这个project实际上没花什么时间,所以水平有限,我也很希望大家能帮我指正和提出建议,希望能借此提高我的编码水平。
这个project的实现难度比较小,不需要分析后台Ajex接口,网页也不是javascript动态渲染得到的,网站基本没什么反爬虫机制,也不需要处理验证码的识别问题。
代码中存在几个小bug:
- 在将抓取的数据插入到数据库中的时候,有时会捕获到异常导致数据插入失败,从而存在一定的数据丢失的问题,我觉得可能是数据插入量太大导致这个问题,但是我暂时不知道怎么解决,希望能有人给我提出改进建议!
- 很不常见的情况下,会无法从服务器获取响应导致数据抓取终止;
1.2 关于用到的参考文档和参考书籍
首先声明,代码都是自己独立完成,并不是从书上抄下来的!
参考书籍:
- 《Python3网络爬虫开发实战》-崔庆才
- 《Python数据可视化》-Kirthi Raman
- 《深入浅出MySQL》
参考文档:
- python 3.6官方文档;
- Requests:让HTTP服务人类;
- Beautiful Soup documentation;
- pyecharts;
1.3 关于数据采集量过小以及可能存在的数据冗余和数据丢失问题的说明
本身代码是能够把IT分类的所有子分类的所有招聘信息抓取完的,但是确实要花很多时间,即便我采用了进程池来减少抓取时间,但是依然会花很多时间,因此这里只是抓取了IT分类中技术类中所有子分类的招聘信息,并且我第一次抓取的时候,代码有一个bug没有解决,就是在猎聘网发布的招聘信息中有一部分是代理发布的,在爬虫尝试去获取该种招聘信息的详细信息的时候在getPageHtml函数中也就是requests会抛出missing schema的异常,从而导致该爬虫进程终止进入下一个子分类信息的抓取(也就是说该子分类的信息抓取到这里就终止了,后面可能存在所有招聘信息都没有抓取到数据库),后来等到第一遍抓取完成后,我解决了这个bug,在代码中可以看得到我将该类信息略过了,本来本着严谨的态度应当重新抓取,但是确实花的时间比较多,而且我只是把这个project作为一个小的练手project来写的,并且实现难度也不高,所以没有完全重新抓取,只是后面补上了一些数据丢失非常严重的子分类的抓取。
由于数据采集量比较小,仅仅只有8万条数据,所以最终的数据可视化分析的结论大多不具有可信度。
由于没有进行对相同信息的筛选过滤,比如说可能有些招聘信息是C++和C两个子分类中都存在,所以存在重复抓取,导致存在一部分数据冗余的现象。
二、python爬虫抓取IT类招聘信息的实现
2.1 代码
"""
coding utf-8
release python3.6
"""
import requests
import lxml
import re
import pymysql
from bs4 import BeautifulSoup
from multiprocessing import Pool
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def getTableName(ID):
"""
有些分类标识符ID中带有MySql数据库表名不支持的符号,该函数返回合法表名
"""
replaceDict={
"Node.JS":"NodeJS",
".NET":"NET",
"C#":"CC",
"C++":"CPP",
"COCOS2D-X":"COCOS2DX"
}
if ID in replaceDict:
return replaceDict[ID]
else:
return ID
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def parseWage(wage):
"""
该函数实现了解析工资字符串wage,如果是'面议'或者其它则返回列表[0,0](代表工资面议),否则返回
相应工资(数值类型,单位为万)
"""
parsedResult=re.findall('(.*?)-(.*?)万.*?',wage,re.S)
if not parsedResult:
return [0,0]
else:
return [parsedResult[0][0],parsedResult[0][1]]
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def table_exists(cursor,table_name):
"""
该函数实现判断某一个表是否在数据库方案里存在,存在返回True,不存在返回False
"""
sql = "show tables;"
cursor.execute(sql)
tables = [cursor.fetchall()]
table_list = re.findall('(\'.*?\')',str(tables))
table_list = [re.sub("'",'',each) for each in table_list]
if table_name in table_list:
return True
else:
return False
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def isUrlValid(url):
"""
由于在爬虫运行过程中发现有些类别招聘信息中含有的详细招聘信息的入口地址在获取响应的时候会抛出Missing Schema异常,
发现是url中有些是.../job/...(往往是猎聘网自己发布的招聘信息),有些是.../a/...(这类招聘信息通常是代理发布),
导致无法解析,从而使爬虫运行到此处时停止抓取数据。
该函数实现对代理发布的URL进行过滤,若为代理发布的信息,则跳过该条招聘信息,函数返回False,否则返回True。
"""
isValid=re.search('.*?www\.liepin\.com/job/.*?$',url,re.S)
if isValid:
return True
else:
return False
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def getPageHtml(url,headers=None):
"""
返回服务器响应页面的html,不成功返回None
"""
if not headers:
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
}
try:
response=requests.get(url,headers=headers)
if response.status_code==200:
return response.text
else:
return None
except requests.RequestException as e:
#debug
print('Exception occur in funciton getPageHtml()')
return None
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def getEntry(html):
"""
解析Html,该函数为生成器类型,每一次迭代返回某一子项目的入口地址URL和description组成的字典entry
"""
if not html:
#html为None则返回None,无法从该html中解析出子项目入口地址
#debug
print('html is None in function getEntry()')
return None
soup=BeautifulSoup(html,'lxml')
for items in soup.find_all(name='li'):
for item in items.find_all(name='dd'):
for usefulURL in item.find_all(name='a',attrs={"target":"_blank","rel":"nofollow"}):
yield{
"URL":'https://www.liepin.com'+usefulURL.attrs['href'],
"URL_Description":usefulURL.text
}
"""--------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------"""
def getCountryEntry(entry):
"""
entry为子项目地址URL和描述URL_Description组成的字典,该函数实现了从子项目页面信息中获取响应,并
且最终返回全国子项目地址CountryURL和CountryURLDescription(实际上就是URL_Description)组成的字典
"""
if not entry:
#debug
print('ERROR in function getCountryEntry:entry is None')
return None
headers={
"Host":"www.liepin.com",
"Referer":"https://www.liepin.com/it/",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36"
}
countryHtml=getPageHtml(entry['URL'],headers=headers)
soup=BeautifulSoup(countryHtml,'lxml')
citiesInfo=soup.find(name='dd',attrs={"data-param":"city"})
if not citiesInfo:
#debug
print('