在上一篇 51job 职位信息的爬取中, 对岗位信息 div 下各式各样杂乱的标签, 简单的 Xpath 效果不佳, 加上 string() 函数后, 也不尽如人意. 因此这次我们跳过桌面 web 端, 选择移动端进行爬取.
一, 代码结构
按照下图所示的爬虫基本框架结构, 我将此份代码分为四个模块 --URL 管理, html 下载, HTML 解析以及数据存储.
二, URL 管理模块
这个模块负责搜索框关键词与对应页面 URL 的生成, 以及搜索结果不同页数的管理. 首先观察某字段 (大数据, UTF-8 为'E5A4A7 E695B0 E68DAE') 全国范围内的结果, 前三页结果的 URL 如下:
URL 前半部分:
这部分中我们可以看到两处处不同, 第一处为编码后'2,?.html'中间的数字, 这是页数. 另一处为参数 stype 的值, 除第一页为空之外, 其余都为 1. 另外, URL 中有一连串的数字, 这些是搜索条件, 如地区, 行业等, 在这儿我没有用上. 后面的一连串字符则为搜索关键词的字符编码. 值得注意的是, 有些符号在 URL 中是不能直接传输的, 如果需要传输的话, 就需要对它们进行编码. 编码的格式为'%'加上该字符的 ASCII 码. 因此在该 URL 中,% 即为符号'%'.
URL 后半部分:
后半部分很明显的就能出首页与后面页面的 URL 参数相差很大, 非首页的 URL 后半部分相同.
因此我们需要对某关键字的搜索结果页面分两次处理, 第一次处理首页, 第二次可使用循环处理后续的页面.
- if __name__ == '__main__':
- key = '数据开发'
- # 第一页
- url = 'https://search.51job.com/list/000000,000000,0000,00,9,99,'+key+',2,1.html?lang=c&stype=&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&providesalary=99&lonlat=0,0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='
- getUrl(url)
- # 后页 [2,100)
- urls = ['https://search.51job.com/list/000000,000000,0000,00,9,99,'+key+',2,{}.html?lang=c&stype=1&postchannel=0000&workyear=99&cotype=99°reefrom=99&jobterm=99&companysize=99&lonlat=0,0&radius=-1&ord_field=0&confirmdate=9&fromType=&dibiaoid=0&address=&line=&specialarea=00&from=&welfare='.format(i) for i in range(2,30)]
- for url in urls:
- getUrl(url)
三, HTML 下载模块
下载 HTML 页面分为两个部分, 其一为下载搜索结果某一页的 HTML 页面, 另一部分为下载某一岗位具体页面. 由于页面中具体岗位 URL 需要从搜索结果页面中获取, 所以将下载搜索结果页面及获取具体岗位 URL 放入一个函数中, 在提取到具体岗位 URL 后, 将其传入至另一函数中.
3.1 搜索结果页面下载与解析
下载页面使用的是 requests 库的 get() 方法, 得到页面文本后, 通过 lxml 库的 etree 将其解析为树状结构, 再通过 Xpath 提取我们想要的信息. 在搜索结果页面中, 我们需要的是具体岗位的 URL, 打开开发者选项, 找到岗位名称.
我们需要的是 < a > 标签里的 href 属性. 右键, 复制 --Xpath, 得到该属性的路径.
//*[@id="resultList"]/div/p/span/a/@href
由于 xpath 返回值为一个列表, 所以通过一个循环, 将列表内 URL 依次传入下一函数.
- def getUrl(url):
- print('New page')
- res = requests.get(url)
- res.encoding = 'GBK'
- if res.status_code == requests.codes.ok:
- selector = etree.HTML(res.text)
- urls = selector.xpath('//*[@id="resultList"]/div/p/span/a/@href')
- # //*[@id="resultList"]/div/p/span/a
- for url in urls:
- parseInfo(url)
- time.sleep(random.randrange(1, 4))
3.2 具体岗位信息页面下载
该函数接收一个具体岗位信息的参数. 由于我们需要对移动端网页进行处理, 所以在发送请求时需要进行一定的伪装. 通过设置 headers, 使用手机浏览器的用户代理, 再调用 get() 方法.
- def parseInfo(url):
- headers = {
- 'User-Agent': 'Opera/9.80 (Android 2.3.4; Linux; Opera Mobi/ADR-1301071546) Presto/2.11.355 Version/12.10'
- }
- res = requests.get(url, headers=headers)
四, HTML 解析模块
在 3.2 中, 我们已经得到了岗位信息的移动端网页源码, 因此再将其转为 etree 树结构, 调用 Xpath 即可得到我们想要的信息.
需要注意的是页面里岗位职责 div 里, 所有相关信息都在一个 < article > 标签下, 而不同页面的 < article > 下层标签并不相同, 所以需要将该标签下所有文字都取出, 此处用上了 string() 函数.
- selector = etree.HTML(res.text)
- title = selector.xpath('//*[@id="pageContent"]/div[1]/div[1]/p/text()')
- salary = selector.xpath('//*[@id="pageContent"]/div[1]/p/text()')
- company = selector.xpath('//*[@id="pageContent"]/div[2]/a[1]/p/text()')
- companyinfo = selector.xpath('//*[@id="pageContent"]/div[2]/a[1]/div/text()')
- companyplace = selector.xpath('//*[@id="pageContent"]/div[2]/a[2]/span/text()')
- place = selector.xpath('//*[@id="pageContent"]/div[1]/div[1]/em/text()')
- exp = selector.xpath('//*[@id="pageContent"]/div[1]/div[2]/span[2]/text()')
- edu = selector.xpath('//*[@id="pageContent"]/div[1]/div[2]/span[3]/text()')
- num = selector.xpath('//*[@id="pageContent"]/div[1]/div[2]/span[1]/text()')
- time = selector.xpath('//*[@id="pageContent"]/div[1]/div[1]/span/text()')
- info = selector.xpath('string(//*[@id="pageContent"]/div[3]/div[2]/article)')
- info = str(info).strip()
五, 数据存储模块
首先创建. CSV 文件, 将不同列名称写入首行.
- fp = open(
- '51job.csv'
- ,
- 'wt'
- ,newline=
- '',encoding='GBK',errors='ignore')
- writer = CSV.writer(fp)
- writer.writerow(('职位','薪资','公司','公司信息','公司地址','地区','工作经验','学历','人数','时间','岗位信息'))
再在解析某一页面数据后, 将数据按行写入. CSV 文件.
writer.writerow((title,salary,company,companyinfo,companyplace,place,exp,edu,num,time,info))
来源: https://www.cnblogs.com/magicxyx/p/10128914.html