"Talk is cheap,Show me the code." 翻译为中文是 "废话少说, 放码过来." 我觉得可谓信达雅.
在编程之路上, 实践的重要性无可比拟. 这也是很多同学感觉学了很多, 但还是不会写代码的原因; 也是很多有意转行的人士, 自学了大半年, 仍不见起色的缘故.
leoxin 在知识星球发起一项活动: 目标是爬取拉钩网的招聘信息以及链家的房产信息, 然后对数据进行清洗和存储, 并分析其数据下的价值, 最后用可视化的形式表现出来. 我觉得难度适中, 循序渐进, 对于不同身份角色的学习人群都大有裨益.
下面我来写一写在第一阶段的一些学习操作总结和感受.
爬虫
构思 / 准备部分
首先打开拉钩网 https://www.lagou.com/jobs/list_Java?px=default&city=上海#filterBox , 我初步选择的是 Java - 上海
Step1
打开浏览器开发者工具, 观察 Network 部分的内容, 首先点进 Doc 部分, 看看服务器给我们返回了哪些文本内容.
在 Preview 预览中, 我们可以看到, 大部分都是一些公共视图框架和公共 JS 代码, 没有核心数据.
那么这时我们就应该猜测, 网站可能是首先渲染一个公共框架, 然后再通过 Ajax 发送请求去获得数据, 再在页面上显示获取的数据.
Step2
通过 Step1 的思考和猜测, 大致确定了数据是异步获取的. 做过一点 web 的应该都想得到这一点, 因为即使是反爬, 也要按照基本法啊! 应该不会使用多么多么匪夷所思的黑科技 (如果真的出现了, 那是我太菜了的原因( °Д °;) )
这时点开 XHR 按钮, XHR 全称 XMLHttpRequest, 有什么作用呢? Ajax 通过 XMLHttpRequest 对象发出 HTTP 请求, 得到服务器返回的数据.
通过预览我们可以发现, 我们需要的数据都出现在 positionAjax 请求下返回的数据中, 参见 content-positionResult-result 中.
那么该如何伪造请求?
点进 Headers, 首先发现 Request Method 的值是 POST, 确定了我们提交请求的方式. 然后看看 Request Headers 中的字段.
发现有好多条.
- Accept:application/json, text/javascript, */*; q=0.01
- Accept-Encoding:gzip, deflate, br
- Accept-Language:zh-CN,zh;q=0.9
- Connection:keep-alive
- Content-Length:23
- Content-Type:application/x-www-form-urlencoded; charset=UTF-8
- Cookie:XXX
- Host:www.lagou.com
- Origin:https://www.lagou.com
- Referer:https://www.lagou.com/jobs/list_Java?px=default&city=上海
- User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/64.0.3282.186 Safari/537.36
- X-Anit-Forge-Code:0
- X-Anit-Forge-Token:None
- X-Requested-With:XMLHttpRequest
通过筛选, 我们选取其中有用的几条, 构建自己的请求头.(那么怎么知道哪些是有用的呢? 首先筛除语言, 编码之类的, 这些的影响一般都是比较小的; 接着在剩下的字段中进行尝试, 等以后有经验了, 自然能准确选取有价值的字段)
由于是 POST 的请求方式, 那么势必会向服务器提交一些数据, 可以看到表单信息:
- first:true
- pn:1
- kd:Java
实现 / 代码部分
Step1
在进行上述分析后, 基本已经准备得差不多了. 这时可以先简单构建一下我们的 Proxy 类.
- class Proxy():
- def __init__(self):
- self.MAX=5 #最大嗅探次数
- self.headers={
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
- "Referer":"https://www.lagou.com/jobs/list_Java?px=default&city=上海",
- "X-Anit-Forge-Code":"0",
- "X-Anit-Forge-Token":"None",
- "X-Requested-With":"XMLHttpRequest"
- }
- def getPage(self,url,data):
- FAILTIME=0 #访问失败次数
- try:
- result=requests.post(url,headers=self.headers,data=data)
- result.encoding = "utf-8"
- return result
- except:
- FAILTIME+=1
- if FAILTIME==self.MAX:
- print("访问错误")
- return ''
上文中提到, 发现 Ajaxposition 返回的 content-positionResult-result 数据, 数据格式是一个数组里有 15 条数据, 每条数据的格式是一个字典, 具体如下:
- adWord:9
- appShow:0
- approve:1
- businessZones:["唐镇", "唐镇", "曹路", "曹路"]
- city:"上海"
- companyFullName:"招商银行股份有限公司信用卡中心"
- companyId:6796
- companyLabelList:["金融科技银行", "技术创新驱动", "奋斗独立改变", "一年两次调薪"]
companyLogo:"i/image2/M00/25/D7/CgoB5lodmL2AJHxrAABieRjcJjU514.png"
- companyShortName:"招商银行信用卡中心"
- companySize:"2000 人以上"
- createTime:"2018-03-09 09:14:30"
- deliver:0
- district:"浦东新区"
- education:"本科"
- explain:null
- financeStage:"上市公司"
- firstType:"开发 / 测试 / 运维类"
- formatCreateTime:"09:14 发布"
- gradeDescription:null
- hitags:null
- imState:"today"
- industryField:"移动互联网, 金融"
- industryLables:[]
- isSchoolJob:0
- jobNature:"全职"
- lastLogin:1520581074000
- latitude:"31.247248"
- linestaion:null
- longitude:"121.673868"
- pcShow:0
- plus:null
- positionAdvantage:"五险一金, 职位晋升, 各类补贴"
- positionId:2762378
- positionLables:["项目管理", "j2ee", "架构"]
- positionName:"Java 技术经理"
- promotionScoreExplain:null
- publisherId:73936
- resumeProcessDay:1
- resumeProcessRate:100
- salary:"30k-50k"
- score:0
- secondType:"管理岗"
- stationname:null
- subwayline:null
- workYear:"5-10 年"
可见返回了大量的信息, 那么我们如何去获得这些数据? 此时可以写一个简单的 Job 类:
- class Job:
- def __init__(self):
- self.datalist=[]
- def getJob(self,url,data):
- p=Proxy()
- result=p.getPage(url,data)
- result.encoding = "utf-8"
- result_dict=result.json()
- try:
- job_info = result_dict['content']['positionResult']['result']
- for info in job_info:
- print(info)
- return job_info
- except:
- print("发生解析错误")
使用 getJob 方法获取的是所有的信息, 这其实是不必要的, 应该也有所选择, 否则将给自己带来压力, 对于后续步骤也将带来不便.
Step2
此时, 一个简单的爬虫已经编写得差不多了, 我们可以进行测试一下. 编写主函数
- if __name__ == '__main__':
- url="https://www.lagou.com/jobs/positionAjax.json?px=default&city=上海&needAddtionalResult=false&isSchoolJob=0"
- job = Job()
- all_page_info=[]
- for x in range(1,31):
- data = {
- "first": "false",
- "pn": x,
- "kd": "Java"
- }
- current_page_info=job.getJob(url,data)
- all_page_info.extend(current_page_info)
- print("第 %d 页已经爬取成功"%x)
- time.sleep(5)
可以看到控制台显示:
总结
到这里, 一个简单的爬虫已经编写完毕了, 数据以 json 格式返回, 似乎已经大功告成. 而事实是, 对于为后面的数据分析做准备工作还差得远, 对于爬取海量数据, 下面有几点思考.
拉钩网对于同一 ip 的大量请求行为肯定会进行封禁, 所以需要准备代理池.
为了实现高自动化, 需要对一系列可能出现的异常情况进行处理, 断点处理, 确保程序不挂.
为了提高效率, 加入多线程.
数据持久化, 在持久化之前需要先进行清洗.
来源: https://juejin.im/post/5ae46a0c518825673c61acaf