- 爬取方法选择
直接爬取:
import requests url = 'https://sou.zhaopin.com/?jl=530&kw=Java%E5%BC%80%E5%8F%91&kt=3' #将爬虫伪装成浏览器请求网页数据 headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.52'} # 可以直接在浏览器的F12界面中查看 r = requests.get(url, headers=headers) print(r.status_code) #200 表示请求成功 print(r.text)
这样请求的只是网页源代码,也就是打开这个网址之后,检查源代码所能查看到的内容
而大多数网页是动态网页,打开这个网址并不会加载所有的内容,所以这种初级的方法不可取
使用自动化工具进行爬取:
也就是使用一个可以自动点击的工具来让想要加载的数据加载出来
这种方法涉及到定位,比较麻烦
查找数据的api接口:
在F12的网络栏目,找到每次加载网页时产生的XHR文件,再查看各自对应的response
这也是较为常用的方式
但有些数据会使用加密(无法通过get方法直接打开),也就是直接爬取是无法执行的
对于这种数据接口,就需要使用抓包工具
- 找到爬取信息所在的网址
在前程无忧的招聘信息栏中找到当前所有的招聘分类,从中选择要爬取的分类
(最好不要通过全局搜素的方式来获得结果,因为这样得到的网址可能是一大堆编码)
比如嵌入式工程师,对应网址为
https://jobs.51job.com/qianrushiyingjian/
翻页后对应网址为
https://jobs.51job.com/qianrushiyingjian/p2/
最后一页网址为
https://jobs.51job.com/qianrushiyingjian/p209/
这样我们就获得了嵌入式硬件开发工程师对应的所有网址
(可以发现这是一个静态网址,翻页的时候F12工具并没有请求XHR文件,同时网址也发生了变化)
(也就说明其可以通过直接爬取的形式来进行操作)
- 定位网页上的信息
通过检查元素获得各个字段在网页中的位置
比如在第一个条目中
职位类别对应的字段位置(此处为full_XPath)为
/html/body/div[4]/div[2]/div[1]/div[2]/div[1]/p[1]/span[1]/a
薪水为
/html/body/div[4]/div[2]/div[1]/div[2]/div[1]/p[1]/span[3]
在最后一个条目中
职位类别对应的字段位置(此处为full_XPath)为
/html/body/div[4]/div[2]/div[1]/div[2]/div[20]/p[1]/span[1]/a
薪水为
/html/body/div[4]/div[2]/div[1]/div[2]/div[20]/p[1]/span[3]
- 使用postman进行测试
postman interceptor直接直接填入浏览器的header和body参数
在浏览器打开网址,其会在postman的历史记录中显示出这个请求,相当于其同步了这个请求
通过检查源代码可以查看其使用的字符集,此处为gbk
<meta http-equiv="Content-Type" content="text/html; charset=gbk">
出现了中文乱码,未解决
对
https://jobs.51job.com/qianrushiyingjian/p8/
使用get方法可以获得html数据
- 使用request库复现postman的请求
def get_data(page): ''' 输入页数,返回一个网页的响应对象 ''' url = 'https://jobs.51job.com/qianrushiyingjian/p'+str(page)+'/' # 请求头 headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "keep-alive", "Cookie": "guid=7e8a970a750a4e74ce237e74ba72856b; partner=blog_csdn_net", "Host": "jobs.51job.com", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36" } # 有请求头写法 res = requests.get(url=url, headers=headers) return res
注意请求头的添加
其都是从浏览器访问的时候抄写的,其作为字典键值对来进行操作
通过正则匹配的方式可以快速将复制的内容替换为想要的键值对形式
import re # 下方引号内添加替换掉请求头内容 headers_str = """ Accept: application/json, text/javascript, */*; q=0.01 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8 Cache-Control: no-cache Connection: keep-alive Content-Length: 77 Content-Type: application/json User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 X-Apple-App-Id: 632 X-Apple-Frame-Id: daw-10beaddd-c22f-4fd4-8aba-ef4909a721dd X-Requested-With: XMLHttpRequest """ pattern = '^(.*?):(.*)$' for line in headers_str.splitlines(): print(re.sub(pattern,'\'\\1\':\'\\2\',',line).replace(' ',''))
操作完成之后如果其返回code 200,则说明请求成功
通过查看response.text发现返回内容存在中文乱码
可以使用response.enconding = "gbk" 来设定返回对象的编码方式
通过如上操作,我们就获得了一个html文件作为数据源
- 使用xpath定位到代码块
需要从lxml模块中导入html
以下为实例
from lxml import html # 模拟一段网页源代码 html_str = ''' <html> <head> <title>这是标题</title> </head> <body> <p>这是内容</p> </body> </html> ''' html_elements = html.etree.HTML(html_str) # 使用了html.etree模块的HTML方法来解析一个网页源代码,获得一个element对象 print(html_elements)
Jobs_element = html_elements.xpath('//div[@class="detlist gbox"]/div')[0]
# 之后就可以对这个element对象使用xpath函数来进行定位
# 注意此处返回的是一个列表,表示查询结果
# 但是一般结果是唯一的,所以索引到0使用就可以了
print(Jobs_element)jobs_str = html.etree.tostring(Jobs_element,enconding='utf-8').decode('gbk')
# 将element对象返回为字符串
# 此处使用decode()方法将其按照gbk进行解码
print(jobs_str)
以上通过xpath方法定位到了某个html中的代码块
def locate_data(res,id,datatype): ''' 输入响应对象,返回各个类型数据对应的列表 ''' re_dict = { 'job':['>.*</a>$',-4], 'company':['>.*</a>$',-4], 'region':['>.*</span>$',-7], 'salary':['>.*</span>$',-7], 'day':['>.*</span>$',-7] } path_dict = { 'job':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[1]/a', 'company':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/a', 'region':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[2]', 'salary':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[3]', 'day':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[4]' } res.encoding = 'gbk' html_elements = html.etree.HTML(res.text) # 使用了html.etree模块的HTML方法来解析一个网页源代码,获得一个element对象 result_element = html_elements.xpath(path_dict[datatype])[0] # 此处返回的是只有一个element元素的列表 try: result_str = html.etree.tostring(result_element,encoding='utf-8').decode('utf-8') result = re.search(re_dict[datatype][0],result_str) result_filltration = result.group(0)[1:re_dict[datatype][1]] print(result_filltration) return result_filltration except AttributeError: print('数据未找到')
# 在对数据进行解析的时候,请设置异常处理,因为数据很有可能为空
注意网站可能是实时刷新的,所以得到的数据可能和网上的不太一样
此时实际上就可以对代码块进行分类(分为工作名称,公司等等对应的代码块),并且写成一个函数
- 使用正则匹配在代码块中定义到文本
将使用re模块
可以分为两个过程,首先是匹配到正则对用的项,然后进一步索引想要的内容
抑或是直接写出与结果内容相匹配的正则表达式,不需要结果附近的内容作辅助匹配
re_dict = { 'job':['>.*</a>$',-4], 'company':['>.*</a>$',-4], 'region':['>.*</span>$',-7], 'salary':['>.*</span>$',-7], 'day':['>.*</span>$',-7] } # 建立了正则式的字典,此处使用了结果附近的内容作辅助匹配 # 实际上也可以直接写出只匹配中文的正则式 result = re.search(re_dict[datatype][0],result_str) # 得到匹配结果 print(result.group(0)[1:re_dict[datatype][1]]) # 进行进一步的索引
- 使用pandas模块导出数据
创建dataframe,将提取到的文本做成列表即可构建成一个完整的数据表
- 完整代码如下
from numpy.lib.twodim_base import mask_indices import requests from lxml import html import re import pandas as pd import time import os os.chdir(r'D:/temp') # 网页链接 def get_data(page): ''' 输入页数,返回一个网页的响应对象 ''' url = 'https://jobs.51job.com/qianrushiyingjian/p'+str(page)+'/' # 请求头 headers = { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Accept-Encoding": "gzip, deflate, br", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "keep-alive", "Cookie": "guid=7e8a970a750a4e74ce237e74ba72856b; partner=blog_csdn_net", "Host": "jobs.51job.com", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36" } # 有请求头写法 res = requests.get(url=url, headers=headers) return res def locate_data(res,id,datatype): ''' 输入响应对象,返回各个类型数据对应的列表 ''' re_dict = { 'job':['>.*</a>$',-4], 'company':['>.*</a>$',-4], 'region':['>.*</span>$',-7], 'salary':['>.*</span>$',-7], 'day':['>.*</span>$',-7] } path_dict = { 'job':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[1]/a', 'company':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/a', 'region':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[2]', 'salary':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[3]', 'day':'/html/body/div[4]/div[2]/div[1]/div[2]/div[' + str(id) + ']/p[1]/span[4]' } res.encoding = 'gbk' html_elements = html.etree.HTML(res.text) # 使用了html.etree模块的HTML方法来解析一个网页源代码,获得一个element对象 result_element = html_elements.xpath(path_dict[datatype])[0] # 此处返回的是只有一个element元素的列表 try: result_str = html.etree.tostring(result_element,encoding='utf-8').decode('utf-8') result = re.search(re_dict[datatype][0],result_str) result_filltration = result.group(0)[1:re_dict[datatype][1]] print(result_filltration) return result_filltration except AttributeError: print('数据未找到') def main(): job_list = [] company_list = [] region_list = [] salary_list = [] day_list = [] for page in range(1,209): time.sleep(5) res = get_data(page) for id in range(1,21): job_list.append(locate_data(res,id,'job')) company_list.append(locate_data(res,id,'company')) region_list.append(locate_data(res,id,'region')) salary_list.append(locate_data(res,id,'salary')) day_list.append(locate_data(res,id,'day')) print(job_list) df_result = (pd.DataFrame([job_list,company_list,region_list,salary_list,day_list],index=['job','company','region','salary','day'])).T df_result.to_csv(r'data.csv',encoding='gbk',index=False) print(df_result) if __name__ == "__main__": main()
- 抓包工具mitmproxy
使用浏览器交互界面mitmweb
其绑定了8080端口,8081是用来运行交互界面的
PS C:\Users\Lee> mitmweb Web server listening at http://127.0.0.1:8081/ Proxy server listening at http://*:8080
PS C:\Users\Lee> mitmdump
Proxy server listening at http://*:8080
使用浏览器通过8080端口来访问127.0.0.1(带参数执行浏览器)
抓包使用的浏览器和被监听的浏览器最好不要是一个
PS C:\Program Files\Google\Chrome\Application> cd "C:\Program Files\Google\Chrome\Application" # 首先cd到浏览器的exe程序所在位置 PS C:\Program Files\Google\Chrome\Application> .\chrome --proxy-server=127.0.0.1:8080 --ignore-certificate-errors # 在power shell中,可以使用.\exe文件名的形式来将exe文件带参数的进行执行
现在在打开的浏览器中进行访问,mitmweb就会显示出相应的请求信息
挖坑待填