- 爬取方法選擇
直接爬取:
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就會顯示出相應的請求信息
挖坑待填