搭建谷歌瀏覽器無頭模式抓取頁面服務,laravel->php->python->docker !!!

timseng發表於2019-08-02

 背景:

公司管理系統需要獲取企業微信頁面的配置引數如企業名、logo、人數等資訊並操作,來隱藏相關敏感資訊並自定義簡化企業號配置流程

第一版已經實現了掃碼登入獲取cookie,使用該cookie就能獲取合法身份隨意請求頁面和介面,所以第一版的模擬操作主要是抓介面,有介面就用沒有就沒的用了

第二版這一版的需要一些配置引數的來源頁面是js渲染上去的,沒有介面,普通的get頁面又不能拿到渲染後的頁面文件,所以只能使用無頭瀏覽器來爬取並操作頁面

實現過程:

laravel版

專案是使用laravel開發,首先想到的是整合到框架裡,而laravel確實提供了相關元件:Laravel Dusk

雖然這個外掛是用來做瀏覽器測試的,但這裡也可以用來爬取頁面

很帥,,但是操作的時候安裝不上去,

 

PHP版

好吧,那就自己實現吧,直接上程式碼

自己封裝了一個類,new的時候直接把之前登入cookie傳過來,這樣就能直接跳頁面了

class QyWebChrome
{
  #下載對應google-chrome版本的驅動https://sites.google.com/a/chromium.org/chromedriver/downloads
    private $envchromedriverpath = 'webdriver.chrome.driver=/usr/bin/chromedriver';

    private $driver;

    private $error;

    public function __construct($cookie_str)
    {
        putenv($this->envchromedriverpath);
        $capabilities = DesiredCapabilities::chrome();
//        $cookie_str ='sdfn=sssf1;; _gxxxx=1';

        //'-headless' 無頭模式:瀏覽器在後臺執行,在安裝了桌面環境的瀏覽器伺服器中可去掉預覽整個過程
        $capabilities->setCapability(
            'chromeOptions',
            ['args' => ['--disable-gpu','-headless','--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36']]
        );

        $this->driver = ChromeDriver::start($capabilities,null);

        sleep(3);
        //先去index設定登入cookie,之後想跳哪個頁面就跳哪個頁面
        $this->driver->get('https://work.weixin.qq.com/');
        sleep(2);
        // adding cookie
        $this->driver->manage()->deleteAllCookies();
        sleep(1);
        $cookie_arr = explode(';',$cookie_str);
        foreach ($cookie_arr as $cookpair){
            $cookie_item = explode('=',$cookpair);
            $cookie=[
                'name'=>trim($cookie_item[0]),
                'value'=>trim($cookie_item[1]),
                'domain'=>'work.weixin.qq.com',
                'httpOnly'=>false,
                'path'=>'/',
                'secure'=>false,
            ];
            $this->driver->manage()->addCookie($cookie);
        }

        sleep(1);

    }

    public function __destruct()
    {
        $this->driver->close();
    }

    //跳轉到我的企業頁面獲取企業資訊
    public function getProfilePage(){
        $data =[];
        $this->driver->get('https://work.weixin.qq.com/wework_admin/frame#profile/enterprise');
        sleep(3);
        //企業logourl

        //企業簡稱
        $companynamespan = $this->driver->findElement(
            WebDriverBy::className('profile_enterprise_item_shareName')
        );
        $data['companyname'] = $companynamespan->getText();

        return $data;
    }

    //獲取渲染後的html
    //$driver->getPageSource();
    /*
     webdriver 主要提供了 2 個 API 來給我們操作 DOM 元素

RemoteWebDriver::findElement(WebDriverBy) 獲取單個元素
RemoteWebDriver::findElements(WebDriverBy) 獲取元素列表
WebDriverBy 是查詢方式物件,提供了下面幾個常用的方式

WebDriverBy::id($id) 根據 ID 查詢元素
WebDriverBy::className($className) 根據 class 查詢元素
WebDriverBy::cssSelector($selctor) 根據通用的 css 選擇器查詢
WebDriverBy::name($name) 根據元素的 name 屬性查詢
WebDriverBy::linkText($text) 根據可見元素的文字錨點查詢
WebDriverBy::tagName($tagName) 根據元素標籤名稱查詢
WebDriverBy::xpath($xpath) 根據 xpath 表示式查詢,這個很強大
     */

    //截圖
    public function takeScrenshot($savepath="test.png"){
        $this->driver->takeScreenshot($savepath);
    }

    /**
     * @return mixed
     */
    public function getError()
    {
        return $this->error;
    }

    /**
     * @param mixed $error
     */
    public function setError($error): void
    {
        $this->error = $error;
    }
}

  部署注意:

先安裝google-chrome

yum install google-chrome

  安裝完成後獲取chrome版

下載對應的chromedriver https://sites.google.com/a/chromium.org/chromedriver/downloads 嗯這個在谷歌

頁面是這個樣子的,主要是googlechrome和chromedirver的對應關係

 

 

這邊網盤各個版本都存了一份 https://pan.baidu.com/s/1xykIgqKUAUm_l0iVHbh6kw  提取碼: hbvz 

執行截圖:

 

  

以為這樣就完成了,沒想到線上上出了問題無法部署!!

wf??原來運維為了保證伺服器能相容低版本的軟體,C的依賴版本安裝的很低,這麼底層的依賴還是不要動了,

解決方案有兩個:

1找臺伺服器安裝高版本的GLIBC_2.14,GLIBC_2.16;

2把爬蟲這塊封裝到docker裡面,對外提供抓取服務,就是到時候直接請求下介面,介面放回抓取的企業微信頁面

因為公司有k8s叢集,所以直接build一個docker更簡單一點,所以選取方案2

Python docker 版

使用docker那就儘量簡單點,直接使用python指令碼,爬蟲還是使用python更猛一些,各種依賴直接pip,之前2017年使用無頭瀏覽器做監控爬蟲的時候驅動還是使用phantomjs呢,現在chrome的headless直接切換過來,api都沒變,

先封裝docker:先去dockers裡把環境搭起來,把相關依賴搞清楚

docker run -it -v /test:/test python:3.7.4 /bin/bash

  

使用/test作為共享目錄,方便宿主機和docker傳輸檔案

先安裝google-chrome,python:3.7.4直接下載deb安裝包 https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb

這有網盤共享 連結: https://pan.baidu.com/s/15rlArB7xCGOHXSko6UUkJA 提取碼: p6d5

docker內安裝google-chrome

然後就是解決依賴,

現在直接上Dockerfile

# Use an official Python runtime as a parent image
FROM python:3.7.4

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

#install depend
RUN apt update && apt -y --fix-broken install libnss3-dev fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libatspi2.0-0 libcups2 libdbus-1-3 libgtk-3-0 libx11-xcb1 libxcomposite1 libxcursor1 libxdamage1 libxfixes3 libxi6 libxrandr2 libxtst6 lsb-release xdg-utils libdbusmenu-glib4 libdbusmenu-gtk3-4 libindicator3-7 libasound2-data libatk1.0-data libavahi-client3 libavahi-common3 adwaita-icon-theme libcolord2 libepoxy0 libjson-glib-1.0-0 librest-0.7-0 libsoup2.4-1 libwayland-client0 libwayland-cursor0 libwayland-egl1 libxinerama1 libxkbcommon0 libgtk-3-common libgtk-3-bin distro-info-data gtk-update-icon-cache libavahi-common-data gtk-update-icon-cache dconf-gsettings-backend libjson-glib-1.0-common libsoup-gnome2.4-1 glib-networking xkb-data dconf-service libdconf1 libproxy1v5 glib-networking-services glib-networking-common gsettings-desktop-schemas default-dbus-session-bus dbus libpam-systemd systemd systemd-sysv libapparmor1 libapparmor1 libcryptsetup12 libidn11 libip4tc0 libkmod2 libargon2-1 libdevmapper1.02.1  libjson-c3 dmsetup \
&& apt-get install -y fonts-wqy-zenhei \
&& dpkg -i google-chrome-stable_current_amd64.deb

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

d +x run.sh

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

#v1 dev Run app.py when the container launches
#CMD ["python", "app.py"]
#v2 production
#CMD ["gunicorn","--config","gunicorn_config.py","app:app"]
#v3 
#ENTRYPOINT [
"gunicorn","--config","gunicorn_config.py","app:app"] #v4 ENTRYPOINT ["./run.sh"]

專案目錄

app.py   處理請求

from flask import Flask
import os
import socket
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
from time import sleep


app = Flask(__name__)

@app.route("/hello/<cookie_str>/<aim_url>/<end_class>")
def hello(cookie_str,aim_url,end_class):
    print(cookie_str)
    print(aim_url)
    print(end_class)
    chrome_options = Options()
    chrome_options.add_argument("--headless")
    chrome_options.add_argument("--no-sandbox")
    chrome_options.add_argument("--disable-gpu")
    chrome_options.add_argument('window-size=1200x600')
    user_ag='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36'
    chrome_options.add_argument('user-agent=%s'%user_ag)

    profile_url = "https://work.weixin.qq.com/wework_admin/frame#profile"
    base_url = "https://work.weixin.qq.com"
    #chromedriver
    driver = webdriver.Chrome(executable_path=(r'/test/chromedriver'), chrome_options=chrome_options)

    #載入首頁設定登入cookie
    driver.get(base_url + "/")
    driver.implicitly_wait(10)
    driver.save_screenshot('screen1.png')
    for coo in cookie_str.split(';'):
        cooki=coo.split('=')
        print(cooki[0].strip())
        print(cooki[1].strip())
        driver.add_cookie({'name':cooki[0].strip(),'value':cooki[1].strip(),'domain':'work.weixin.qq.com','httpOnly':False,'path':'/','secure':False})

    driver.implicitly_wait(10)

    #跳轉目標頁面
    driver.get(profile_url)
    WebDriverWait(driver,20,0.5).until(EC.presence_of_element_located((By.CLASS_NAME, 'ww_commonCntHead_title_inner_text')))
    #sleep(5)
    driver.save_screenshot('screen.png')

    driver.close()
    return "hhhhhh $s" % cookie_str

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80)

  

run.sh

#!/bin/bash
set -e
pwd

touch access.log error.log

exec gunicorn app:app \
        --bind 0.0.0.0:80 \
        --workers 4 \
        --timeout 120 \
        --log-level debug \
        --access-logfile=access.log \
        --error-logfile=error.log

exec "$@"

  

requirements.txt

Flask
selenium
gunicorn

  

flask的內建伺服器開發的時候能用,線上部署的時候使用官方推薦的gunicorn部署,這裡直接用了gunicorn執行

gunicorn的啟動配置後來寫進run.sh了,所以gunicorn_config.py就沒用了

docker 映象構建

docker build -t mypythonflask:v6 .

docker啟動命令

docker run -d -v /data:/data -p 8888:80 -v /dev/shm:/dev/shm mypythonflask:v6

這裡的/dev/shm是為了解決當載入的頁面過大或者載入大圖docker記憶體不夠瀏覽器爆掉 解決方案來著 https://stackoverflow.com/questions/39936240/selenium-error-in-python-webdriverexception-unknown-error-session-deleted-bec/57302028#57302028

Selenium error in python: WebDriverException: unknown error: session deleted because of page crash from tab crashed

  

 請求測試

[root@localhost testdockerchrome]# curl "http://localhost:8888/hello/sss=sss;%20_ssst=1/bb/cc"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

#處理時間太長導致超時,檢查下截圖

 

  

這曲折的實現歷程。。。

至此,爬取服務搭建完畢,後面只要是處理一下業務相關的東西,比如擴充app.py的功能,使其支援更多的操作

總結下來就是使用docker部署了一個服務,該服務接收登入cookie,url,配置等引數,使用chrome的headless模式抓取頁面操作頁面,返回結果,擴充瀏覽器操作可以寫在app.py中

 

^-^原創文章,轉載請宣告出處

 

相關文章