Python爬取猎聘网的数据进行分析
Posted 小李敲码
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python爬取猎聘网的数据进行分析相关的知识,希望对你有一定的参考价值。
前言:
一、选题的背景
近年来,越来越多的年轻人在寻找工作这个方面呢的事情上会出现各种问题,而好的工作非常难找,差的工作很多年轻人也不想做,所以我选择做一份数据分析一下招聘网站上各个工作的情况。
二、项目目标分析
本项目是对猎聘网的数据进行爬取分析,主要分析的目标是招聘信息,学历要求等;
分析在猎聘网中寻找的工作招聘信息,薪资以及其他福利待遇,以及对求职者的学历要求要多高进行分析。
三、网络爬虫设计方案
(1)爬虫名称:爬取猎聘网的数据进行分析
参考书籍:
- 《Python3网络爬虫开发实战》-崔庆才
- 《Python数据可视化》-Kirthi Raman
- 《深入浅出mysql》
参考文档:
- python 3.6官方文档;
- Requests:让HTTP服务人类;
- Beautiful Soup documentation;
- pyecharts;
四、python爬虫抓取IT类招聘信息的实现
2.1、代码
1 mport requests 2 import lxml 3 import re 4 import pymysql 5 from bs4 import BeautifulSoup 6 from multiprocessing import Pool 7 8 def getTableName(ID): 9 """ 10 有些分类标识符ID中带有MySql数据库表名不支持的符号,该函数返回合法表名 11 """ 12 replaceDict={ 13 "Node.JS":"NodeJS", 14 ".NET":"NET", 15 "C#":"CC", 16 "C++":"CPP", 17 "COCOS2D-X":"COCOS2DX" 18 } 19 if ID in replaceDict: 20 return replaceDict[ID] 21 else: 22 return ID 23 24 def parseWage(wage): 25 """ 26 该函数实现了解析工资字符串wage,如果是\'面议\'或者其它则返回列表[0,0](代表工资面议),否则返回 27 相应工资(数值类型,单位为万) 28 """ 29 parsedResult=re.findall(\'(.*?)-(.*?)万.*?\',wage,re.S) 30 if not parsedResult: 31 return [0,0] 32 else: 33 return [parsedResult[0][0],parsedResult[0][1]] 34 35 36 """ 37 该函数实现判断某一个表是否在数据库方案里存在,存在返回True,不存在返回False 38 """ 39 sql = "show tables;" 40 cursor.execute(sql) 41 tables = [cursor.fetchall()] 42 table_list = re.findall(\'(\\\'.*?\\\')\',str(tables)) 43 table_list = [re.sub("\'",\'\',each) for each in table_list] 44 if table_name in table_list: 45 return True 46 else: 47 return False 48 def isUrlValid(url): 49 """ 50 由于在爬虫运行过程中发现有些类别招聘信息中含有的详细招聘信息的入口地址在获取响应的时候会抛出Missing Schema异常, 51 发现是url中有些是.../job/...(往往是猎聘网自己发布的招聘信息),有些是.../a/...(这类招聘信息通常是代理发布), 52 导致无法解析,从而使爬虫运行到此处时停止抓取数据。 53 该函数实现对代理发布的URL进行过滤,若为代理发布的信息,则跳过该条招聘信息,函数返回False,否则返回True。 54 """ 55 isValid=re.search(\'.*?www\\.liepin\\.com/job/.*?$\',url,re.S) 56 if isValid: 57 return True 58 else: 59 return False 60 61 def getPagehtml(url,headers=None): 62 """ 63 返回服务器响应页面的html,不成功返回None 64 """ 65 if not headers: 66 headers={ 67 "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", 68 } 69 70 try: 71 response=requests.get(url,headers=headers) 72 if response.status_code==200: 73 return response.text 74 else: 75 return None 76 except requests.RequestException as e: 77 #debug 78 print(\'Exception occur in funciton getPageHtml()\') 79 80 return None 81 def getEntry(html): 82 """ 83 解析Html,该函数为生成器类型,每一次迭代返回某一子项目的入口地址URL和description组成的字典entry 84 """ 85 86 if not html: 87 #html为None则返回None,无法从该html中解析出子项目入口地址 88 #debug 89 print(\'html is None in function getEntry()\') 90 return None 91 92 soup=BeautifulSoup(html,\'lxml\') 93 for items in soup.find_all(name=\'li\'): 94 for item in items.find_all(name=\'dd\'): 95 for usefulURL in item.find_all(name=\'a\',attrs={"target":"_blank","rel":"nofollow"}): 96 yield{ 97 "URL":\'https://www.liepin.com\'+usefulURL.attrs[\'href\'], 98 "URL_Description":usefulURL.text 99 } 100 101 102 def getCountryEntry(entry): 103 """ 104 entry为子项目地址URL和描述URL_Description组成的字典,该函数实现了从子项目页面信息中获取响应,并 105 且最终返回全国子项目地址CountryURL和CountryURLDescription(实际上就是URL_Description)组成的字典 106 """ 107 108 if not entry: 109 #debug 110 print(\'ERROR in function getCountryEntry:entry is None\') 111 return None 112 113 headers={ 114 "Host":"www.liepin.com", 115 "Referer":"https://www.liepin.com/it/", 116 "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" 117 } 118 119 countryHtml=getPageHtml(entry[\'URL\'],headers=headers) 120 soup=BeautifulSoup(countryHtml,\'lxml\') 121 citiesInfo=soup.find(name=\'dd\',attrs={"data-param":"city"}) 122 123 if not citiesInfo: 124 #debug 125 print(\'ERROR in function getCountryEntry():citiesInfo is None.\') 126 return None 127 128 db=pymysql.connect(host=\'localhost\',user=\'root\',password=\'123456\',port=3306,db=\'spider\') 129 cursor=db.cursor() 130 if not table_exists(cursor,entry[\'URL_Description\']): 131 createTableSql="""CREATE TABLE IF NOT EXISTS spider.{} like spider.positiondescription;""".format(getTableName(entry[\'URL_Description\'])) 132 try: 133 cursor.execute(createTableSql) 134 print(\'--------------create table %s--------------------\' % (entry[\'URL_Description\'])) 135 except: 136 print(\'error in function getCountryEntry():create table failed.\') 137 finally: 138 db.close() 139 140 141 142 return { 143 "CountryURL":"https://www.liepin.com"+citiesInfo.find(name=\'a\',attrs={"rel":"nofollow"}).attrs[\'href\'], 144 "CountryURLDescription":entry[\'URL_Description\'] 145 } 146 147 def getCountryEmployeeInfo(CountryEntry): 148 """ 149 CountryEntry是getCountryEntry函数返回的由全国招聘信息CountryURL和地址分类描述 150 CountryURLDescription构成的字典,该函数提取出想要的信息 151 """ 152 153 if not CountryEntry: 154 #debug 155 print(\'ERROR in function getCountryEmpolyeeInfo():CountryEntry is None.\') 156 return None 157 158 db=pymysql.connect(host=\'localhost\',user=\'root\',password=\'123456\',port=3306,db=\'spider\') 159 cursor=db.cursor() 160 161 indexOfPage=0 162 theMaxLength=0 163 164 #遍历该类招聘信息的每一页 165 while indexOfPage<=theMaxLength: 166 URL=CountryEntry[\'CountryURL\']+\'&curPage=\'+str(indexOfPage) 167 pageHtml=getPageHtml(URL) 168 soup=BeautifulSoup(pageHtml,\'lxml\') 169 170 #提取出该类招聘信息总共的页数,仅需要提取一次即可 171 if indexOfPage==0: 172 prepareReString=soup.find(name=\'a\',attrs={"class":"go","href":"javascript:;"}).attrs[\'onclick\'] 173 pattern=re.compile(\'Math\\.min\\(Math\\.max\\(\\$pn,\\s\\d\\),(.*?)\\)\') 174 theMaxLength=int(re.findall(pattern,prepareReString)[0]) 175 176 #debug,检测访问到第几页 177 print(\'Accessing page {} of {}\'.format(indexOfPage,CountryEntry[\'CountryURLDescription\'])) 178 #进入下一页 179 indexOfPage+=1 180 181 """ 182 这里代码实现对信息的有效信息的提取 183 """ 184 for detailedDescriptionURL in getDetailedDescriptionURL(soup): 185 #如果详细招聘信息入口URL是代理发布(即无效,这里不爬取这类信息),则跳过该条招聘信息 186 if not isUrlValid(detailedDescriptionURL): 187 continue 188 detailedDescriptionHtml=getPageHtml(detailedDescriptionURL) 189 #将分区标识符(例如java、php等)添加进返回的字典 190 result=detailedInformation(detailedDescriptionHtml) 191 result[\'ID\']=CountryEntry[\'CountryURLDescription\'] 192 """ 193 if \'ID\' in result: 194 print(type(result[\'ID\']),\'>>>\',result) 195 """ 196 if result[\'Available\']: 197 #获取工资最小值和最大值 198 min_max=parseWage(result[\'wage\']) 199 #有些公司没有福利tag 200 reallyTag=\'\' 201 if not result[\'tag\']: 202 reallyTag=\'无\' 203 else: 204 reallyTag=result[\'tag\'] 205 206 insertSql="""insert into spider.{} values(0,\'{}\',\'{}\',\'{}\',{},{},\'{}\',\'{}\',\'{}\',\'{}\',\'{}\',\'{}\',\'{}\');""".format(getTableName(result[\'ID\']),result[\'position\'],result[\'company\'],result[\'wage\'],min_max[0],min_max[1],result[\'education\'],result[\'workExperience\'],result[\'language\'],result[\'age\'],result[\'description\'],reallyTag,result[\'workPlace\']) 207 208 try: 209 cursor.execute(insertSql) 210 db.commit() 211 except: 212 db.rollback() 213 #debug 214 print(\'ERROR in function getCountryEmployeeInfo():execute sql failed.\') 215 #爬取完该类招聘信息之后关闭数据库连接 216 db.close() 217 218 def getDetailedDescriptionURL(soup): 219 """ 220 soup为全国招聘信息列表页面解析的BeautifulSoup对象,该函数为生成器,每一次迭代产生一条招聘信息 221 详细内容的URL字符串 222 """ 223 if not soup: 224 #debug 225 print(\'ERROR in function getDetailedDescroption():soup is None.\') 226 return None 227 228 for item in soup.find_all(name=\'div\',attrs={"class":"job-info"}): 229 detailedDescriptionURL=item.find(name=\'a\',attrs={"target":"_blank"}).attrs[\'href\'] 230 yield detailedDescriptionURL 231 232 233 def detailedInformation(detailedDescriptionHtml): 234 """ 235 该函数实现对具体的一条详细招聘信息的提取,detailedDescriptionHtml为一条详细招聘信息网页的 236 HTML,该函数返回值为职位具体要求构成的字典positionDescription 237 """ 238 if not detailedDescriptionHtml: 239 #debug 240 print(\'ERROR in function detailedInformation():detailedDescriptionHtml is None.\') 241 return None 242 243 soup=BeautifulSoup(detailedDescriptionHtml,\'lxml\') 244 245 #提取出招聘职位和公司,类型为str 246 positionItem=soup.find(name=\'div\',attrs={"class":"title-info"}) 247 #有时候招聘信息被删除了但是招聘信息的入口仍然留在招聘列表中,这里就是防止这种情况导致运行失败 248 if not positionItem: 249 return { 250 \'Available\':False 251 } 252 position=positionItem.h1.text 253 company=soup.find(name=\'div\',attrs={"class":"title-info"}).a.text 254 255 #提取出工资水平(类型为str,有些是面议)、工作地点、学历要求、工作经验、语言要求和年龄要求 256 items=soup.find(name=\'div\',attrs={"class":"job-title-left"}) 257 wage=items.find(name=\'p\',attrs={"class":"job-item-title"}).text.split(\'\\r\')[0] 258 workPlace=items.find(name=\'a\') 259 #有些工作地点在国外,该网站不提供该地区招聘信息的网页,没有标签a,这里就是处理这样的异常情况 260 if not workPlace: 261 workPlace=items.find(name=\'p\',attrs={"class":"basic-infor"}).span.text.strip() 262 else: 263 workPlace=workPlace.text 264 265 #这里返回一个大小为4的列表,分别对应学历要求、工作经验、语言要求、年龄要求 266 allFourNec=items.find(name=\'div\',attrs={"class":"job-qualifications"}).find_all(name=\'span\') 267 268 #有些招聘信息中带有公司包含的福利tag,这里也提取出来,所有tag组成一个由-分隔的字符串,没有则为空字符串 269 tagItems=soup.find(name=\'ul\',attrs={"class":"comp-tag-list clearfix","data-selector":"comp-tag-list"}) 270 tags=\'\' 271 if tagItems: 272 tempTags=[] 273 for tag in tagItems.find_all(name=\'span\'): 274 tempTags.append(tag.text) 275 tags=\'-\'.join(tempTags) 276 277 #提取出详细的职位技能要求 278 descriptionItems=soup.find(name=\'div\',attrs={"class":"job-item main-message job-description"}) 279 description=descriptionItems.find(name=\'div\',attrs={"class":"content content-word"}).text.strip() 280 281 positionDescription={ 282 "Available":True, 283 "position":position, 284 "company":company, 285 "wage":wage, 286 "workPlace":workPlace, 287 "猎聘网架构中间件负责人:基于Flume+Kafka+ Elasticsearch+Storm的海量日志实时分析平台实习精选大数据研究院西门子猎聘网网易云音乐百词斩等名企实习精选
2021年大数据ELK:使用VSCode操作猎聘网职位搜索案例
2021年大数据ELK:使用VSCode操作猎聘网职位搜索案例