BOSS 直聘整站爬取思路總結


一、目標網站

BOSS 直聘 (www.zhipin.com)

二、分析思路

考慮到要進行整站爬取, 首先要熟悉這個網站的各個板塊結構是怎么樣的. 

首先用瀏覽器訪問 BOSS 直聘首頁 (www.zhipin.com). 在首頁面, 按照從上到下從左到右的順序瀏覽各個板塊, 通過分析比較各個板塊內容, 決定采用左側導航欄提供的職位分類來根據職位結合城市信息獲取整站數據.

分析頁面源碼, 解析出所有職位分類信息以及深層鏈接, 將鏈接拼接完整, 根據數據特點構建數據結構, 將構建好的數據結構采用 MongoDB 進行持久化. (這里采用 MongoDB 的原因是其為文檔型數據庫, 存儲數據較為靈活, 不用像 MySQL 這種關系型數據庫需要事先考慮表結構以及字段相關的設計) 到此, 對整站所有職位按照分類已全部解析入庫, 接下來需要根據職位分類結合城市信息獲取到所有城市的招聘崗位信息.

采用抽樣分析, 隨機選擇幾個職位與城市的組合進行查詢, 對比查詢結果發現是以 URL 參數影響最終查詢結果的. 職位編號唯一對應某一個職位, 城市編號唯一對應某一個城市. 由上面的步驟獲取到了所有職位對應的鏈接地址, 其中包含職位的編號, 因此這里不需要再考慮職位編號. 點擊頁面上 所有城市 標簽, 彈出一個模態框可進行城市選擇, 考慮此為一個異步請求, 接着分析頁面元素, 發現 所有城市 對應 <a> 標簽存在點擊事件監聽, 跳轉到對應 JS 代碼中繼續分析其所綁定的點擊事件, 在當前 JS 文件中根據關鍵字進行全文搜索, 配合 Debug 添加斷點調試 JS 代碼, 最終獲取到 Ajax 請求地址, 嘗試將該地址拼接完整后模擬瀏覽器請求, 得到包含所有城市信息的 JSON 數據, 從中可得到所有城市對應的其唯一編號, 根據獲取到的 JSON 數據的特點構建數據結構存儲所有城市以及其對應的唯一編號, 采用 MongoDB 進行持久化.

上述步驟獲取到了所有職位的鏈接地址以及城市編號作為查詢參數, 接下來將鏈接與參數進行組合, 通過進一步的請求即可得到所有職位在全國各個城市的招聘崗位信息.

將上述鏈接與參數信息從數據庫中取出進行組合, 對組合后新的鏈接發起請求, 解析響應數據獲取基本的招聘崗位信息以及詳情頁鏈接, 將詳情頁鏈接拼接完整並進一步請求獲取詳情頁響應數據進行解析入庫

三、部分代碼展示

# 導入相關模塊
import datetime

import requests
import xlsxwriter
from pymongo import MongoClient
from lxml import etree
# 基本配置
# 創建數據庫連接
client = MongoClient("localhost", 27017)
# 初始化數據庫
db = client["Bosspro"]

# 設置請求頭
headers = {"user-agent": (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
        "AppleWebKit/537.36 (KHTML, like Gecko)"
        "Chrome/79.0.3915.0 Safari/537.36 Edg/79.0.287.3"
    )
}

# 設置目標 URL
url = "https://www.zhipin.com"
# 數據解析
# 獲取響應數據
response = requests.get(url=url, headers=headers)
# 獲取 HTML 源文件內容
page_html = response.text
# 轉換為 etree 對象
page_tree = etree.HTML(page_html)
dl_list = page_tree.xpath('//div[@class="job-menu"]//dl')

# 數據結構
"""
    [
        "category": "A"
        "sub_category": [
            {
                "node": "a",
                "data": [
                    {
                        "position": "a1",
                        "param": "/a1/001/"
                    },
                    {
                        "position": "a2",
                        "param": "/a2/001"
                    }
                ]
            },
            {
                "node": "b",
                "data": [
                    {
                        "position": "b1",
                        "param": "/b1/001/"
                    },
                    {
                        "position": "b2",
                        "param": "/b2/001/"
                    }
                ]
            }
        ]
    ]
"""
data_list = []
for dl in dl_list:
    category = dl.xpath('./div[2]/p/text()')[0]
    li_list = dl.xpath('./div[2]/ul//li')
    
    li_data_dict = {
        "category": category,
        "sub_category": [
            {
                "node": li.xpath('./h4/text()')[0],
                "data": [
                    {
                        "position": a.xpath('./text()')[0],
                        "param": a.xpath('./@href')[0]
                    } for a in li.xpath('./div//a')
                ]
            } for li in li_list
        ]
    }
    
    data_list.append(li_data_dict)
# 持久化
# 將分好類的職位數據以構造好的數據結構存入數據庫
# 檢測數據更新, 若最新數據與數據庫中數據存在差異則更新為最新數據
for data in data_list:
    db.position.update_one(
        data,
        {"$setOnInsert": data},
        upsert=True
    )

data = db.position.find({})

# 網站數據更新, 數據庫更新並去重
d = {}
for dic in data:
    d.setdefault(dic["category"], [])
    d.get(dic["category"]).append(1)
for k, v in d.items():
    if len(v) > 1:
        delete_obj = db.position.find_one({"category": k})
        db.position.delete_one(delete_obj)
# 其他數據請求
# 獲取城市信息及其唯一編碼
url = "https://www.zhipin.com/wapi/zpCommon/data/city.json"
response = requests.get(url=url, headers=headers)
page_json = response.json()
zpData = page_json["zpData"]["cityList"]

# 數據結構
"""
    [
        {
            'name': '北京',
            'subLevelModelList': [
                {
                    'name': '北京',
                    'code': '101010100'
                }
            ]
        },
        {
            'name': '上海',
            'subLevelModelList': [
                {
                    'name': '上海',
                    'code': '101020100'
                }
            ]
        }, {
            'name': '天津',
            'subLevelModelList': [
                {
                    'name': '天津',
                    'code': '101030100'
                }
            ]
        }
    ]
"""

country_data_list = [
    {
        "name": dic["name"], "subLevelModelList": [
            {
                "name": subLevelModel["name"],
                "code": str(subLevelModel["code"])
            } for subLevelModel in dic["subLevelModelList"]
        ] 
    } for dic in zpData]
# 其他數據持久化
# 將分好類的職位數據以構造好的數據結構存入數據庫
for data in country_data_list:

    db.country.update_one(
        data,
        {"$setOnInsert": data},
        upsert=True
    )

data = db.country.find({})

# 網站數據更新, 數據庫更新並去重
d = {}
for dic in data:
    d.setdefault(dic["name"], [])
    d.get(dic["name"]).append(1)
for k, v in d.items():
    if len(v) > 1:
        delete_obj = db.country.find_one({"name": k})
        db.country.delete_one(delete_obj)
# 生成 xls 文件
# 將分好類的職位數據存入 Excel
workbook = xlsxwriter.Workbook('{}.xlsx'.format(datetime.date.today()))

cell_format = workbook.add_format({
    'border': 1,
    'align': 'center',
    'valign': 'vcenter',
    'text_wrap': 1
})
merge_format = workbook.add_format({
    'bold': True,
    'border': 1,
    'align': 'center',
    'valign': 'vcenter',
    'text_wrap': 1
})

param_list = []
for dic in data_list:
    row = 0
    col = 0
    worksheet = workbook.add_worksheet(dic["category"].replace("/", ""))
    worksheet.set_column(0, 0, 20)
    worksheet.set_column(1, 1, 30)
    worksheet.set_column(2, 2, 50)
    
    worksheet.write(0, 0, "職位大類", merge_format)
    worksheet.write(0, 1, "職位細分", merge_format)
    
    country_col = 2
    for country in country_data_list:
        worksheet.write(0, country_col, country["name"], merge_format)
        worksheet.set_column(country_col, country_col, 50)
        country_col += 1
        
    for node_dic in dic["sub_category"]:
        if len(node_dic["data"]) == 1:
            worksheet.write(
                row+1,
                col,
                '{}\n({}個細分職業)'.format(node_dic["node"], len(node_dic["data"])),
                cell_format
            )
        else:
            worksheet.merge_range(
                row+1,
                col,
                row+len(node_dic["data"]),
                col,
                '{}\n({}個細分職業)'.format(node_dic["node"], len(node_dic["data"])),
                cell_format
            )

        data_row = row + 1
        data_col = col + 1
        for data in node_dic["data"]:
            country_data_col = data_col + 1
            
            worksheet.write(data_row, data_col, data["position"], cell_format)
            for country in country_data_list:  
                code = country["subLevelModelList"][0]["code"]
                param_list.append(data["param"])
                
                worksheet.write(
                    data_row,
                    country_data_col,
                    data["param"],
                    cell_format
                )
                
                country_data_col += 1
            
            data_row += 1
            
        row += len(node_dic["data"])
    
workbook.close()

 四、部分數據展示

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM