1. 程式人生 > >在centos7上部署selenium(基於chrome驅動)的爬蟲專案

在centos7上部署selenium(基於chrome驅動)的爬蟲專案

 相信大家在寫爬蟲的時候,經常會遇到爬取的網站是動態渲染的,而且各自反爬加密引數,難以破解,所以不得已採用使用了python+selenium進行模擬人為操作爬取。免去了一些繁瑣步驟。但是我們大多數都是在windows或者Mac下進行開發和測試。開發完了之後。最終要部署到伺服器上去。那麼伺服器常用的就有liunx。

至於liunx伺服器我們都知道,它並沒有一個像windows上的桌面,而是一個純命令列的介面。所以也就沒有所謂的Chrome瀏覽器之類的。在部署selenium專案的時候,需要開啟Chrome的無頭模式。也就是沒有介面的瀏覽器。由於我在實際部署上centos7上執行的時候,遇到了很多的問題,各種坑,總是會報各種錯誤使得selenium專案不能正常執行。通過網上搜集和整理了一些資料動手操作,最終嘗試了千百次後成功的部署並正常執行爬取了。這裡將我的經驗以筆記形式記下來,希望對需要的同仁有所參考:

環境準備

首先我們需要準備好一臺centos7的liunx伺服器,例如我這裡是核心為:3.10.0-862.el7.x86_64 的伺服器,如下圖所示:

我們可以使用:uname -r 命令檢視核心版本,這裡建議使用3.10以上的版本。然後我們需要安裝Python和專案中用到的一些庫。我這裡安裝的Python版本是3.6.4的,如下圖所示:

安裝好Python之後,接下來,我們來配置關於selenium的一些環境。相關步驟如下:

步驟1:下載Chrome

wget https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm

步驟2:安裝Chrome

yum install ./google-chrome-stable_current_x86_64.rpm

步驟3:配置chromedriver

注意chromedriver的版本,要與你安裝的chrome版本對應上,這裡的版本已經不是最新的。版本列表:http://chromedriver.chromium.org/downloads

步驟4:以我這裡為例,下載chromedriver_linux64.zip:

wget https://chromedriver.storage.googleapis.com/2.38/chromedriver_linux64.zip

然後解壓:解壓chromedriver_linux64.zip

unzip chromedriver_linux64.zip

步驟5:為chromedriver授權

chmod 755 chromedriver

步驟6:Python程式碼測試

例如使用以下程式碼:

from selenium import webdriver

def spider(url='http://bing.com'):
    option = webdriver.ChromeOptions()
    option.add_argument('--no-sandbox')  
    option.add_argument('--headless')  
    # 注意path,我這裡是chromedriver放在/home/apk/chromedriver
    driver = webdriver.Chrome(executable_path='/home/apk/chromedriver', chrome_options=option)
    driver.get(url)
    print(driver.page_source)
spider()

執行程式碼,如下圖所示表示已經環境配置已經成功:

 

可以看到,它已經成功的返回了許多html程式碼。

環境配置成功以後,接下來就可以將我們python+selenium寫的爬蟲程式碼部署上去啦。最後需要注意兩點最關鍵的程式碼:

#開啟無頭模式
options.add_argument('--headless')
#這個命令禁止沙箱模式,否則肯能會報錯遇到chrome異常。
options.add_argument('--no-sandbox')

這兩個引數特別重要,不然執行會報錯,因為liunx下是沒有介面的。

這裡給出一個示例我自己的爬取同城旅遊網機票資訊的爬蟲:

from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
import time
import re
from lxml import etree
import platform
from threading import Thread


#判斷傳入的日期是否是當前月份的日期
def is_same_month(date_str):
    same_month=time.strftime('%Y-%m', time.localtime(time.time()))
    if date_str[0:7]==same_month:
        return True
    else:
        return False
#多執行緒非同步方法執行封裝
def async(func):
    def wrapper(*args, **kwargs):
        thr = Thread(target = func, args = args, kwargs = kwargs)
        thr.start()
    return wrapper

#獲取選中的天
def get_day(date_str):

    if date_str==str(time.strftime('%Y-%m-%d',time.localtime(time.time()))):
        return "今天"
    elif date_str=="2019-01-01":
        return "元旦"
    elif date_str=="2018-12-25":
        return "聖誕"
    elif date_str=="2019-02-04":
        return "除夕"
    elif date_str=="2019-02-04":
        return "春節"
    elif date_str=="2019-02-14":
        return "情人"
    elif date_str=="2019-02-19":
        return "元宵"
    elif date_str=="2019-04-05":
        return "清明"
    elif date_str=="2019-05-01":
        return "五一"
    elif date_str=="2019-06-01":
        return "兒童"
    elif date_str=="2019-06-07":
        return "端午"
    elif date_str=="2019-08-07":
        return "七夕"
    elif date_str=="2019-09-13":
        return "中秋節"
    elif date_str=="2019-10-01":
        return "國慶"
    elif date_str=="2019-12-25":
        return "聖誕"
    elif date_str=="2019-06-07":
        return "端午"
    else:
        return str(int(date_str.split("-")[2]))

'''
使用selenium自動化測試工具爬取同城旅遊網機票資訊
爬取URL:https://www.ly.com
author:liu-yanlin
依賴環境:python3.6.1
pip install selenium==3.13.0
pip install lxml==4.2.1
Chrome驅動下載地址:https://pan.baidu.com/s/1564mrLmlT7vPdLBntm8hlQ 提取碼:fq33
'''
class LySpider():

    '''
    @:param date_str 查詢日期
    @:param start_city 查詢起始城市
    @:param arrive_city 查詢抵達城市
    '''
    def __init__(self,date_str=None,start_city=None,arrive_city=None):
        self.date_str=date_str
        self.start_city=start_city
        self.arrive_city=arrive_city
        options = Options()
        #開啟無頭模式
        options.add_argument('--headless')
        #這個命令禁止沙箱模式,否則肯能會報錯遇到chrome異常。
        options.add_argument('--no-sandbox')
        sys_str = platform.system()
        if sys_str=="Linux":
            self.driver = webdriver.Chrome(executable_path='/home/apk/chromedriver', chrome_options=options)
        else:
            self.driver = webdriver.Chrome(chrome_options=options)
            # self.driver = webdriver.Chrome()

    '''
    通過selenium控制Chrome驅動,完成模擬人工輸入查詢地址和日期然後點選提交獲取查詢結果html的流程
    '''
    def get_query_results(self):

        # 隱性等待和顯性等待可以同時用,但要注意:等待的最長時間取兩者之中的大者
        self.driver.implicitly_wait(10)
        self.driver.get('https://www.ly.com/FlightQuery.aspx')
        locator = (By.ID, 'txtAirplaneCity1')
        try:
            #顯性等待
            WebDriverWait(self.driver, 20, 0.5).until(EC.presence_of_element_located(locator))
            # 起始地城市input元素獲取並清空值,然後填入城市名稱,輸入之後模擬按回車鍵
            txtAirplaneCity1 = self.driver.find_elements_by_id("txtAirplaneCity1")[0]
            txtAirplaneCity1.clear()
            txtAirplaneCity1.send_keys(self.start_city)

            txtAirplaneCity1.send_keys(Keys.ENTER)
            # 抵達地城市input元素獲取並清空值,然後填入城市名稱,輸入之後模擬按回車鍵
            txtAirplaneCity2 = self.driver.find_elements_by_id("txtAirplaneCity2")[0]
            txtAirplaneCity2.clear()
            txtAirplaneCity2.send_keys(self.arrive_city)
            txtAirplaneCity2.send_keys(Keys.ENTER)

            # 如果所查詢的日期在當月範圍內,則定位到日曆外掛中第1個div否則定位到第2個div,div1 表示當月,div2表示下一個月
            if is_same_month(self.date_str):
                # 定位到日曆外掛
                element_calendar = self.driver.find_elements_by_xpath(
                    "/html/body/div[17]/div/div[1]/div[1]/div/table/tbody/tr/td/span")
                for item in element_calendar:
                    if item.text == get_day(self.date_str):
                        item.click()
            else:
                element_calendar = self.driver.find_elements_by_xpath(
                    "/html/body/div[17]/div/div[1]/div[2]/div/table/tbody/tr/td/span")
                for item in element_calendar:
                    if item.text == get_day(self.date_str):
                        item.click()
            # 定位搜尋按鈕並模擬點選提交
            airplaneSubmit = self.driver.find_elements_by_id("airplaneSubmit")[0]
            airplaneSubmit.click()
            # 顯性等待後,定位到機票查詢結果div,然後獲取div內的html
            locator_content = (By.ID, 'allFlightListDom_1')
            WebDriverWait(self.driver, 20, 0.5).until(EC.presence_of_element_located(locator_content))
            flight_list_html=self.get_flight_list_dom()
            #返回結果
            data_list=[]
            '''
            此處判斷返回的flight_list_html裡面是否包含有機票資訊,如果有直接返回此html程式碼,否則使用for迴圈
            從新嘗試10次,每迴圈一次暫停一秒(這裡為啥要這樣寫,因為實際情況中可能會存在網路延遲載入慢等原因
            導致獲取不到內容)
            '''
            if flight_list_html:
                for item in flight_list_html:
                    data_list.append(item.get_attribute('innerHTML'))
            else:
                for x in range(10):
                    flight_list_html = self.get_flight_list_dom()
                    if flight_list_html:
                        for item in flight_list_html:
                            data_list.append(item.get_attribute('innerHTML'))
                        break
                    time.sleep(1)
            return data_list

        except Exception as ex:
            print(ex)
        finally:
            self.driver.close()
    '''
    定位到機票查詢結果div,然後獲取div內的html
    '''
    def get_flight_list_dom(self):
        # ---顯性等待後,定位到機票查詢結果div,然後獲取div內的html
        #通過觀察頁面發現這個機票列表資料有三種格式,所以將它們都提取出來拼接成一個List返回
        flight_list_html_n=self.driver.find_elements_by_xpath('//div[@class="clearfix flightList"]//div[@class="flist_box"]')
        flight_list_html_top=self.driver.find_elements_by_xpath('//div[@class="clearfix flightList"]//div[@class="flist_box f_m_top flist_boxat"]')
        flight_list_html_boxbot = self.driver.find_elements_by_xpath('//div[@class="clearfix flightList"]//div[@class="flist_box flist_boxbot"]')
        return flight_list_html_n+flight_list_html_top+flight_list_html_boxbot

    '''
    提取資料
    @:param respone get_query_results()方法中返回的結果內容
    '''
    def extract(self,respone):
        try:
            data_list=[]
            for item in respone:
                data = {}
                html = etree.HTML(item)
                # 航司
                airline = html.xpath('/html/body/table/tbody/tr/td[1]/div[1]/text()')
                data["airline"] = airline[0] if airline else ""
                # 航班號
                flight_number = re.findall("[a-zA-Z]{2}\d+", airline[0])+re.findall("\d[a-zA-Z]{1}\d+", airline[0])
                data["flight_number"] = flight_number[0] if flight_number else ""
                # 出發時間
                dep_time = html.xpath('/html/body/table/tbody/tr/td[2]/div[1]/text()')
                data["dep_time"] = dep_time[0] if dep_time else ""
                # 出發機場
                dep_airport = html.xpath('/html/body/table/tbody/tr/td[2]/div[2]/text()')
                data["dep_airport"] = dep_airport[0] if dep_airport else ""
                # 飛機型別
                aircraft_type = html.xpath('/html/body/table/tbody/tr/td[1]/div[2]/a/text()')
                data["aircraft_type"] = aircraft_type[0] if aircraft_type else ""
                # 抵達時間
                arr_time = html.xpath('/html/body/table/tbody/tr/td[4]/div[1]/text()')
                data["arr_time"] = arr_time[0] if arr_time else ""
                # 抵達機場
                arr_airport = html.xpath('/html/body/table/tbody/tr/td[4]/div[2]/text()')
                data["arr_airport"] = arr_airport[0] if arr_airport else ""
                # 價格
                price = html.xpath('/html/body/table/tbody/tr/td[8]/div[1]/span[1]/em[1]/text()')
                data["price"] = price[0] if price else ""
                #出發日期
                data["date_str"]=self.date_str
                data_list.append(data)
            return data_list
        except Exception as ex:
            print(ex)
            return None
    '''
    儲存資料
    @:param data 要儲存的資料,預設是儲存extract()方法所返回的資料
    '''
    def save(self,data=None):
        try:
            #以下將資料儲存到kafka中
            if data:
                pass
            else:
                results=self.extract(self.get_query_results())
        except Exception as ex:
            pass

if __name__ == "__main__":

    @async
    def run_spider(date):
        print("-------進入 {} ----------爬取".format(date))
        ly_spider = LySpider(date,"成都","北京")
        res=ly_spider.get_query_results()
        data_list=ly_spider.extract(res)
        print("---------完成時間-----------")
        print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
        for item in data_list:
            print(item)

    #date_list=["2019-01-01","2019-01-02","2019-01-03","2019-01-04","2019-01-05","2019-01-06","2019-01-07","2019-01-08","2019-01-09","2019-01-10"]
    date_list = ["2019-01-01", "2019-01-02", "2019-01-03"]
    print(time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())))
    for x in date_list:
        run_spider(x)

    # date="2018-12-20"
    # ly_spider = LySpider(date, "成都", "北京")
    # res = ly_spider.get_query_results()
    # data_list = ly_spider.extract(res)
    # print("-------------爬取結果----------------")
    # for item in data_list:
    #     print(item)

執行結果如圖所示: