Selenium&Chrome實戰:動態爬取51job招聘信息


一、概述

Selenium自動化測試工具,可模擬用戶輸入,選擇,提交。

爬蟲實現的功能:

  1. 輸入python,選擇地點:上海,北京 ---->就去爬取上海,北京2個城市python招聘信息
  2. 輸入會計,選擇地址:廣州,深圳,杭州---->就去爬取廣州,深圳,杭州3個城市會計招聘信息
  3. 根據輸入的不同,動態爬取結果 

 

二、頁面分析

輸入關鍵字

selenium怎么模擬用戶輸入關鍵字,怎么選擇城市,怎么點擊搜索按鈕?

 

Selenium模擬用戶輸入關鍵字,谷歌瀏覽器右鍵輸入框,點檢查,查看代碼

 

通過selenium的find_element_by_id 找到 id = 'kwdselectid',然后send_keys('關鍵字')即可模擬用戶輸入

代碼為:

textElement = browser.find_element_by_id('kwdselectid')
textElement.send_keys('python')

 

選擇城市

selenium模擬用戶選擇城市--- (這個就難了,踩了很多坑)

點擊城市選擇,會彈出一個框

 

然后選擇:北京,上海,  右鍵檢查,查看源代碼

 

可以發現:value的值變成了"北京+上海"

那么是否可以用selenium找到這個標簽,更改它的屬性值為"北京+上海",可以實現選擇城市呢?

答案:不行,因為經過自己的幾次嘗試,發現真正生效的是下面的"010000,020000",這個是什么?城市編號,也就是說在輸入"北京+上海",實際上輸入的是:"010000,020000", 那這個城市編號怎么來的,這個就需要去爬取51job彈出城市選擇框那個頁面了,頁面代碼里面有城市對應的編號

 

獲取城市編號

getcity.py代碼:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import json


# 設置selenium使用chrome的無頭模式
chrome_options = Options()
chrome_options.add_argument("--headless")
# 在啟動瀏覽器時加入配置
browser = webdriver.Chrome(options=chrome_options)
cookies = browser.get_cookies()
browser.delete_all_cookies()
browser.get('https://www.51job.com/')
browser.implicitly_wait(20)

# 找到城市選擇框,並模擬點擊
button = browser.find_element_by_xpath("//div[@class='ush top_wrap']//div[@class='el on']/p\
[@class='addbut']//input[@id='work_position_input']").click()

# 選中城市彈出框
browser.current_window_handle

# 定義一個空字典
dic = {}

# 找到城市,和對應的城市編號
find_city_elements = browser.find_elements_by_xpath("//div[@id='work_position_layer']//\
div[@id='work_position_click_center_right_list_000000']//tbody/tr/td")
for element in find_city_elements:
    number = element.find_element_by_xpath("./em").get_attribute("data-value")  # 城市編號
    city = element.find_element_by_xpath("./em").text  # 城市
    # 添加到字典
    dic.setdefault(city, number)
print(dic)
# 寫入文件
with open('city.txt', 'w', encoding='utf8') as f:
    f.write(json.dumps(dic, ensure_ascii=False))
browser.quit()

執行輸出:

{'北京': '010000', '上海': '020000', '廣州': '030200', '深圳': '040000', '武漢': '180200', '西安': '200200', '杭州': '080200', '南京': '070200', '成都': '090200', '重慶': '060000', '東莞': '030800', '大連': '230300', '沈陽': '230200', '蘇州': '070300', '昆明': '250200', '長沙': '190200', '合肥': '150200', '寧波': '080300', '鄭州': '170200', '天津': '050000', '青島': '120300', '濟南': '120200', '哈爾濱': '220200', '長春': '240200', '福州': '110200'}

 

通過selenium的find_element_by_xpath 找到城市編號這個input,然后讀取city.txt文件,把對應的城市替換為城市編號,在用selenium執行js代碼,就可以加載城市了---代碼有點長,完整代碼寫在后面

 

selenium模擬用戶點擊搜索

通過selenium的find_element_by_xpath 找到 這個button按鈕,然后click() 即可模擬用戶點擊搜索

代碼為:

browser.find_element_by_xpath("//div[@class='ush top_wrap']/button").click()

 

以上都是模擬用戶搜索的行為,下面就是對數據提取規則

先定位總頁數:158頁

 

 

找到每個崗位詳細的鏈接地址:

 

 

最后定位需要爬取的數據

崗位名,薪水,公司名,招聘信息,福利待遇,崗位職責,任職要求,上班地點,工作地點 這些數據,總之需要什么數據,就爬什么

需要打開崗位詳細的鏈接,比如:https://jobs.51job.com/shanghai-mhq/118338654.html?s=01&t=0

 

 

三、完整代碼

代碼介紹

新建目錄51cto-selenium,結構如下:

./
├── get51Job.py
├── getcity.py
└── mylog.py

文件說明:

getcity.py  (首先運行)獲取城市編號,會生成一個city.txt文件

mylog.py     日志程序,記錄爬取過程中的一些信息

get51Job.py 爬蟲主程序,里面包含:

Item類  定義需要獲取的數據

GetJobInfo類 主程序類

getBrowser方法     設置selenium使用chrome的無頭模式,打開目標網站,返回browser對象

userInput方法        模擬用戶輸入關鍵字,選擇城市,點擊搜索,返回browser對象

getUrl方法               找到所有符合規則的url,返回urls列表

spider方法               提取每個崗位url的詳情,返回items

getresponsecontent方法  接收url,打開目標網站,返回html內容

piplines方法            處理所有的數據,保存為51job.txt

getPageNext方法   找到總頁數,並獲取下個頁面的url,保存數據,直到所有頁面爬取完畢

 

getcity.py

# !/usr/bin/python3
# -*- coding: utf-8 -*-

#!/usr/bin/env python
# coding: utf-8
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import json


# 設置selenium使用chrome的無頭模式
chrome_options = Options()
chrome_options.add_argument("--headless")
# 在啟動瀏覽器時加入配置
browser = webdriver.Chrome(options=chrome_options)
cookies = browser.get_cookies()
browser.delete_all_cookies()
browser.get('https://www.51job.com/')
browser.implicitly_wait(20)

# 找到城市選擇框,並模擬點擊
button = browser.find_element_by_xpath("//div[@class='ush top_wrap']//div[@class='el on']/p\
[@class='addbut']//input[@id='work_position_input']").click()

# 選中城市彈出框
browser.current_window_handle

# 定義一個空字典
dic = {}

# 找到城市,和對應的城市編號
find_city_elements = browser.find_elements_by_xpath("//div[@id='work_position_layer']//\
div[@id='work_position_click_center_right_list_000000']//tbody/tr/td")
for element in find_city_elements:
    number = element.find_element_by_xpath("./em").get_attribute("data-value")  # 城市編號
    city = element.find_element_by_xpath("./em").text  # 城市
    # 添加到字典
    dic.setdefault(city, number)
print(dic)
# 寫入文件
with open('city.txt', 'w', encoding='utf8') as f:
    f.write(json.dumps(dic, ensure_ascii=False))
browser.quit()
View Code

 

get51Job.py

# !/usr/bin/python3
# -*- coding: utf-8 -*-
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from mylog import MyLog as mylog
import json
import time
import requests
from lxml import etree


class Item(object):
    job_name = None  # 崗位名
    company_name = None  # 公司名
    work_place = None  # 工作地點
    salary = None  # 薪資
    release_time = None  # 發布時間
    job_recruitment_details = None  # 招聘崗位詳細
    job_number_details = None  # 招聘人數詳細
    company_treatment_details = None  # 福利待遇詳細
    practice_mode = None  # 聯系方式


class GetJobInfo(object):
    """
    the all data from 51job.com
    所有數據來自前程無憂招聘網
    """
    def __init__(self):
        self.log = mylog()  # 實例化mylog類,用於記錄日志
        self.startUrl = 'https://www.51job.com/'  # 爬取的目標網站
        self.browser = self.getBrowser()  # 設置chrome
        self.browser_input = self.userInput(self.browser)  # 模擬用戶輸入搜索
        self.getPageNext(self.browser_input)   # 找到下個頁面

    def getBrowser(self):
        """
        設置selenium使用chrome的無頭模式
        打開目標網站 https://www.51job.com/
        :return: browser
        """
        try:
            # 創建chrome參數對象
            chrome_options = Options()
            # 把chrome設置成無界面模式,不論windows還是linux都可以,自動適配對應參數
            chrome_options.add_argument("--headless")
            # 在啟動瀏覽器時加入配置
            browser = webdriver.Chrome(options=chrome_options)
            # 利用selenium打開網站
            browser.get(self.startUrl)
            # 等待網站js代碼加載完畢
            browser.implicitly_wait(20)
        except Exception as e:
            # 記錄錯誤日志
            self.log.error('打開目標網站失敗:{},錯誤代碼:{}'.format(self.startUrl, e))
        else:
            # 記錄成功日志
            self.log.info('打開目標網站成功:{}'.format(self.startUrl))
            # 返回實例化selenium對象
            return browser

    def userInput(self, browser):
        """
        北京 上海 廣州 深圳 武漢 西安 杭州
        南京  成都 重慶 東莞 大連 沈陽 蘇州
        昆明 長沙 合肥 寧波 鄭州 天津 青島
        濟南 哈爾濱 長春 福州
        只支持以上城市,輸入其它則無效
        最多可選5個城市,每個城市用 , 隔開(英文逗號)
        :return:browser
        """
        time.sleep(1)
        # 用戶輸入關鍵字搜索
        search_for_jobs = input("請輸入職位搜索關鍵字:")
        # 用戶輸入城市
        print(self.userInput.__doc__)
        select_city = input("輸入城市信息,最多可輸入5個,多個城市以逗號隔開:")
        # 找到51job首頁上關鍵字輸入框
        textElement = browser.find_element_by_id('kwdselectid')
        # 模擬用戶輸入關鍵字
        textElement.send_keys(search_for_jobs)

        # 找到城市選擇彈出框,模擬選擇"北京,上海,廣州,深圳,杭州"
        button = browser.find_element_by_xpath("//div[@class='ush top_wrap']\
        //div[@class='el on']/p[@class='addbut']//input[@id='jobarea']")

        # 打開城市對應編號文件
        with open("city.txt", 'r', encoding='utf8') as f:
            city_number = f.read()
            # 使用json解析文件
            city_number = json.loads(city_number)

        new_list = []
        # 判斷是否輸入多值
        if len(select_city.split(',')) > 1:
            for i in select_city.split(','):
                if i in city_number.keys():
                    # 把城市替換成對應的城市編號
                    i = city_number.get(i)
                    new_list.append(i)
                    # 把用戶輸入的城市替換成城市編號
            select_city = ','.join(new_list)
        else:
            for i in select_city.split(','):
                i = city_number.get(i)
                new_list.append(i)
            select_city = ','.join(new_list)

        # 執行js代碼
        browser.execute_script("arguments[0].value = '{}';".format(select_city), button)

        # 模擬點擊搜索
        browser.find_element_by_xpath("//div[@class='ush top_wrap']/button").click()
        self.log.info("模擬搜索輸入成功,獲取目標爬取title信息:{}".format(browser.title))
        return browser

    def getPageNext(self, browser):
        # 找到總頁數
        str_sumPage = browser.find_element_by_xpath("//div[@class='p_in']/span[@class='td'][1]").text
        sumpage = ''
        for i in str_sumPage:
            if i.isdigit():
                sumpage += i

        # sumpage = 1
        self.log.info("獲取總頁數:{}".format(sumpage))
        s = 1
        while s <= int(sumpage):
            urls = self.getUrl(self.browser)
            # 獲取每個崗位的詳情
            self.items = self.spider(urls)
            # 數據下載
            self.pipelines(self.items)
            # 清空urls列表,獲取后面的url(去重,防止數據重復爬取)
            urls.clear()
            s += 1
            self.log.info('開始爬取第%d頁' % s)
            # 找到下一頁的按鈕點擊
            # NextTag = browser.find_element_by_partial_link_text("下一頁").click()
            NextTag = browser.find_element_by_class_name('next').click()
            # 等待加載js代碼
            browser.implicitly_wait(20)
            time.sleep(3)
        self.log.info('獲取所有崗位成功')
        # browser.quit()

    def getUrl(self, browser):
        # 創建一個空列表,用來存放所有崗位詳情的url
        urls = []

        # 創建一個特殊招聘空列表
        job_urls = []

        # 獲取所有崗位詳情url
        Elements = browser.find_elements_by_xpath("//div[@class='j_joblist']//div[@class='e']")
        for element in Elements:
            try:
                url = element.find_element_by_xpath("./a").get_attribute("href")
                title = element.find_element_by_xpath('./a/p/span[@class="jname at"]').get_attribute('title')
            except Exception as e:
                self.log.error("獲取崗位詳情失敗,錯誤代碼:{}".format(e))
            else:
                # 排除特殊的url,可單獨處理
                src_url = url.split('/')[3]
                if src_url == 'sc':
                    job_urls.append(url)
                    self.log.info("獲取不符合爬取規則的詳情成功:{},添加到job_urls".format(url))
                else:
                    urls.append(url)
                    self.log.info("獲取詳情成功:{},添加到urls".format(url))

        return urls

    def spider(self, urls):
        # 數據過濾,爬取需要的數據,返回items列表
        items = []
        for url in urls:
            htmlcontent = self.getreponsecontent(url)
            html_xpath = etree.HTML(htmlcontent)
            item = Item()
            # 崗位名
            job_name = html_xpath.xpath("normalize-space(//div[@class='cn']/h1/text())")
            item.job_name = job_name

            # 公司名
            company_name = html_xpath.xpath("normalize-space(//div[@class='cn']\
                        /p[@class='cname']/a/text())")
            item.company_name = company_name
            # 工作地點
            work_place = html_xpath.xpath("normalize-space(//div[@class='cn']\
                        //p[@class='msg ltype']/text())").split('|')[0].strip()
            item.work_place = work_place
            # 薪資
            salary = html_xpath.xpath("normalize-space(//div[@class='cn']/strong/text())")
            item.salary = salary
            # 發布時間
            release_time = html_xpath.xpath("normalize-space(//div[@class='cn']\
                        //p[@class='msg ltype']/text())").split('|')[-1].strip()
            item.release_time = release_time
            # 招聘崗位詳細
            job_recruitment_details_tmp = html_xpath.xpath("//div[@class='bmsg job_msg inbox']//text()")
            if not job_recruitment_details_tmp:
                break

            item.job_recruitment_details = ''
            ss = job_recruitment_details_tmp.index("職能類別:")
            ceshi = job_recruitment_details_tmp[:ss - 1]
            for i in ceshi:
                item.job_recruitment_details = item.job_recruitment_details + i.strip() + '\n'
            # 招聘人數詳細
            job_number_details_tmp = html_xpath.xpath("normalize-space(//div[@class='cn']\
            //p[@class='msg ltype']/text())").split('|')
            item.job_number_details = ''
            for i in job_number_details_tmp:
                item.job_number_details = item.job_number_details + ' ' + i.strip()
            # 福利待遇詳細
            company_treatment_details_tmp = html_xpath.xpath("//div[@class='t1']//text()")
            item.company_treatment_details = ''
            for i in company_treatment_details_tmp:
                item.company_treatment_details = item.company_treatment_details + ' ' + i.strip()
            # 聯系方式
            practice_mode_tmp = html_xpath.xpath("//div[@class='bmsg inbox']/p//text()")
            item.practice_mode = ''
            for i in practice_mode_tmp:
                item.practice_mode = item.practice_mode + ' ' + i.strip()
            items.append(item)
        return items

    def getreponsecontent(self, url):
        # 接收url,打開目標網站,返回html
        fakeHeaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 \
        (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36'}
        try:
            response = requests.get(url=url,headers=fakeHeaders)
            # 利用apparent_encoding,自動設置編碼
            response.encoding = response.apparent_encoding
            html = response.text
        except Exception as e:
            self.log.error(u'Python 返回 url:{} 數據失敗\n錯誤代碼:{}\n'.format(url, e))
        else:
            self.log.info(u'Python 返回 url:{} 數據成功\n'.format(url))
            time.sleep(1)  # 1秒返回一個結果  手動設置延遲防止被封
            return html

    def pipelines(self, items):  # 接收一個items列表
        # 數據下載
        filename = u'51job.txt'
        with open(filename, 'a', encoding='utf-8') as fp:
            for item in items:
                fp.write('job_name:{}\ncompany_name:{}\nwork_place:{}\nsalary:\
                {}\nrelease_time:{}\njob_recruitment_details:{}\njob_number_details:\
                {}\ncompany_treatment_details:\{}\n\
                practice_mode:{}\n\n\n\n' \
                         .format(item.job_name, item.company_name, item.work_place,
                                 item.salary, item.release_time,item.job_recruitment_details,
                                 item.job_number_details, item.company_treatment_details,
                                 item.practice_mode))
                self.log.info(u'崗位{}保存到{}成功'.format(item.job_name, filename))


if __name__ == '__main__':
    st = GetJobInfo()
View Code

 

mylog.py

# !/usr/bin/python3
# -*- coding: utf-8 -*-
import logging
import getpass
import sys


# 定義MyLog類
class MyLog(object):
    def __init__(self):
        self.user = getpass.getuser()  # 獲取用戶
        self.logger = logging.getLogger(self.user)
        self.logger.setLevel(logging.DEBUG)

        # 日志文件名
        self.logfile = sys.argv[0][0:-3] + '.log'  # 動態獲取調用文件的名字
        self.formatter = logging.Formatter('%(asctime)-12s %(levelname)-8s %(message)-12s\r\n')

        # 日志顯示到屏幕上並輸出到日志文件內
        self.logHand = logging.FileHandler(self.logfile, encoding='utf-8')
        self.logHand.setFormatter(self.formatter)
        self.logHand.setLevel(logging.DEBUG)

        self.logHandSt = logging.StreamHandler()
        self.logHandSt.setFormatter(self.formatter)
        self.logHandSt.setLevel(logging.DEBUG)

        self.logger.addHandler(self.logHand)
        self.logger.addHandler(self.logHandSt)

    # 日志的5個級別對應以下的5個函數
    def debug(self, msg):
        self.logger.debug(msg)

    def info(self, msg):
        self.logger.info(msg)

    def warn(self, msg):
        self.logger.warning(msg)

    def error(self, msg):
        self.logger.error(msg)

    def critical(self, msg):
        self.logger.critical(msg)


if __name__ == '__main__':
    mylog = MyLog()
    mylog.debug(u"I'm debug 中文測試")
    mylog.info(u"I'm info 中文測試")
    mylog.warn(u"I'm warn 中文測試")
    mylog.error(u"I'm error 中文測試")
    mylog.critical(u"I'm critical 中文測試")
View Code

 

運行程序

需要先運行getcity.py,獲取城市編號,運行結果如下

{'北京': '010000', '上海': '020000', '廣州': '030200', '深圳': '040000', '武漢': '180200', '西安': '200200', '杭州': '080200', '南京': '070200', '成都': '090200', '重慶': '060000', '東莞': '030800', '大連': '230300', '沈陽': '230200', '蘇州': '070300', '昆明': '250200', '長沙': '190200', '合肥': '150200', '寧波': '080300', '鄭州': '170200', '天津': '050000', '青島': '120300', '濟南': '120200', '哈爾濱': '220200', '長春': '240200', '福州': '110200'}

 

在運行主程序get51Job.py

關鍵字輸入: python

城市選擇:上海

pycharm運行截圖:

 

 

生成的文件51job.txt截圖

 

 

根據輸入結果的不同,爬取不同的信息,利用selenium可以做到動態爬取

注意:如果遇到51job頁面改版,本程序運行會報錯。請根據實際情況,修改對應的爬蟲規則。

 

 

本文參考鏈接:
http://www.py3study.com/Article/details/id/344.html


免責聲明!

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



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