『No15: Go 實(shí)現(xiàn) python 庫 fake-useragent』

go-15.png
15.png

大家好,我叫謝偉,是一名程序員。

在未來人人都是手藝人。

傳統(tǒng)的手藝人在圈子內(nèi)創(chuàng)造影響力,在互聯(lián)網(wǎng)時(shí)代,個(gè)人影響力不僅僅限于圈子內(nèi),互聯(lián)網(wǎng)創(chuàng)造無限可能性。

好,今天的主題:作品意識(shí)

1、作品意識(shí)

作品很好理解,比如歌手發(fā)行唱片、發(fā)行單曲,作家寫書等,前端工程師可能更容易出作品,比如,寫一個(gè)優(yōu)雅的網(wǎng)站,比如寫一個(gè)優(yōu)雅的工具,寫一個(gè)實(shí)用的小程序,開發(fā)一個(gè)個(gè)人的APP等,這些都是作品。

后端人員,可以寫庫,雖然在和真實(shí)用戶交互層面,后端工程師開發(fā)的工具大概只能在程序員內(nèi)使用,或者有一定編程基礎(chǔ)的人才能使用。

盡管不是每個(gè)人寫的工具都能得到廣泛的認(rèn)可、使用。作為程序員,一定要有作品意識(shí),什么意思?

  • 寫用戶交互友好的工具
  • 寫符合人性的工具
  • 寫簡潔的工具

對于后端開發(fā)者來說最終體現(xiàn)出來的作品便是“不斷的創(chuàng)造輪子、或者重復(fù)造輪子”。

關(guān)于是否重復(fù)造輪子,知乎有廣泛的討論,我的觀點(diǎn)是:想要提高個(gè)人的技能水平,需要不斷的造輪子,水平低,造簡單的輪子,水平漸漸提升,造更高級(jí)點(diǎn)的輪子。在過程參考優(yōu)秀開源工具的實(shí)現(xiàn)、思想。

模仿是最簡單的學(xué)習(xí)方式

2、如何產(chǎn)出作品

在工作之余,我較長時(shí)間放在 Github 上。去發(fā)現(xiàn)一些好的項(xiàng)目,去參考一些好的效果。當(dāng)然作為后端開發(fā)者,我不太會(huì)去看前端的項(xiàng)目,同樣,作為Gopher ,我傾向于 關(guān)注 Go 項(xiàng)目,但如果是火熱的 Python 項(xiàng)目,那我也會(huì)關(guān)注下。

隨著關(guān)注點(diǎn)的越來越精細(xì),我傾向于從我熟悉的東西入手,什么意思,為什么從熟悉的東西入手,因?yàn)槲以絹碓桨l(fā)現(xiàn),自信心是很重要的,如果你不能第一時(shí)間對一個(gè)項(xiàng)目提起興趣和自信心,你可能沒什么機(jī)會(huì)和這個(gè)項(xiàng)目產(chǎn)生化學(xué)反應(yīng)。

近期在閱讀 requests-html

  • 我比較熟悉爬蟲
  • 這是一個(gè)網(wǎng)頁信息解析的庫
  • 代碼量不是很大,閱讀起來簡便
import sys
import asyncio
from urllib.parse import urlparse, urlunparse, urljoin
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures._base import TimeoutError
from functools import partial
from typing import Set, Union, List, MutableMapping, Optional

import pyppeteer
import requests
from pyquery import PyQuery

from fake_useragent import UserAgent
from lxml.html.clean import Cleaner
import lxml
from lxml import etree
from lxml.html import HtmlElement
from lxml.html import tostring as lxml_html_tostring
from lxml.html.soupparser import fromstring as soup_parse
from parse import search as parse_search
from parse import findall, Result
from w3lib.encoding import html_to_unicode

從導(dǎo)入的庫來看,這個(gè)網(wǎng)頁解析的庫主要是整合了各種第三方庫和內(nèi)置庫,這意味著,很少有直接從零開始造輪子,可以借助已經(jīng)比較優(yōu)秀的第三方庫。

好,關(guān)于這個(gè)庫的分析,我下次講。

取其中的一個(gè)庫來分析:fake_useragent

這是一個(gè)生成UserAgent 的庫,可以指定瀏覽器的類型,也可以隨機(jī)生成UserAgent。

我一不小心對它產(chǎn)生了興趣。

網(wǎng)上一般的講解如何隨機(jī)生存UserAgent 的處理方法是,在本地緩存一個(gè)大的文件,隨機(jī)從文件內(nèi)取一個(gè)。當(dāng)然這看上去不夠極客唉。

1、使用

from fake_useragent import UserAgent
ua = UserAgent()

ua.ie
# Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US);
ua.msie
# Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)'
ua['Internet Explorer']
# Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)
ua.opera
# Opera/9.80 (X11; Linux i686; U; ru) Presto/2.8.131 Version/11.11
ua.chrome
# Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2'
ua.google
# Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/537.13 (KHTML, like Gecko) Chrome/24.0.1290.1 Safari/537.13
ua['google chrome']
# Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11
ua.firefox
# Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/16.0.1
ua.ff
# Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:15.0) Gecko/20100101 Firefox/15.0.1
ua.safari
# Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25

# and the best one, random via real world browser usage statistic
ua.random

用法十分簡單,畢竟完成的功能就不是很復(fù)雜。

  • 隨機(jī)生成
  • 指定瀏覽器生成

2、閱讀 fake_useragent 文檔

fake_useragent

可以看出,其實(shí)是從網(wǎng)上抓取到的信息,在隨機(jī)生成的。

那作者如何選擇瀏覽器?只選擇這幾個(gè)瀏覽器的理由是?

image.png

可以查看當(dāng)前最主流的瀏覽器的一些數(shù)據(jù)。

由于一些大家都知道的原因,這個(gè)網(wǎng)站的訪問需要借助一些手段。

Sometimes, useragentstring.com or w3schools.com changes their html, or down, in such case fake-useragent uses CDN cloudfront fallback

意味著還可以從這個(gè)網(wǎng)站上獲取UserAgent

3、梳理

  • 這是一個(gè)獲取 UserAgent 的庫
  • 主要的數(shù)據(jù)來自兩個(gè)網(wǎng)站
  • 根據(jù)統(tǒng)計(jì)數(shù)據(jù)得出主流的瀏覽器

本質(zhì)是一個(gè)爬蟲

4、源代碼

    def load(self):
        try:
            with self.load.lock:
                if self.cache:
                    self.data = load_cached(
                        self.path,
                        use_cache_server=self.use_cache_server,
                        verify_ssl=self.verify_ssl,
                    )
                else:
                    self.data = load(
                        use_cache_server=self.use_cache_server,
                        verify_ssl=self.verify_ssl,
                    )

                # TODO: change source file format
                # version 0.1.4+ migration tool
                self.data_randomize = list(self.data['randomize'].values())
                self.data_browsers = self.data['browsers']
        except FakeUserAgentError:
            if self.fallback is None:
                raise
            else:
                logger.warning(
                    'Error occurred during fetching data, '
                    'but was suppressed with fallback.',
                )
    def __getattr__(self, attr):
        if attr in self.safe_attrs:
            return super(UserAgent, self).__getattr__(attr)

        try:
            for value, replacement in settings.REPLACEMENTS.items():
                attr = attr.replace(value, replacement)

            attr = attr.lower()

            if attr == 'random':
                browser = random.choice(self.data_randomize)
            else:
                browser = settings.SHORTCUTS.get(attr, attr)

            return random.choice(self.data_browsers[browser])
        except (KeyError, IndexError):
            if self.fallback is None:
                raise FakeUserAgentError('Error occurred during getting browser')  # noqa
            else:
                logger.warning(
                    'Error occurred during getting browser, '
                    'but was suppressed with fallback.',
                )

                return self.fallback

核心代碼是這兩個(gè)函數(shù)。

數(shù)據(jù)來源:


CACHE_SERVER = 'http://d2g6u4gh6d9rq0.cloudfront.net/browsers/fake_useragent_{version}.json'.format(  # noqa
    version=__version__,
)

BROWSERS_STATS_PAGE = 'https://www.w3schools.com/browsers/default.asp'

BROWSER_BASE_PAGE = 'http://useragentstring.com/pages/useragentstring.php?name={browser}'  # noqa

5、實(shí)現(xiàn)

你已經(jīng)知道了這是個(gè)Python 庫。好了,你也了解了這個(gè)庫的核心代碼和思想。

你下一步怎么做?

重新實(shí)現(xiàn)。

你可以選擇 Python 實(shí)現(xiàn),但是在你看源代碼的過程中,你的思維應(yīng)該已經(jīng)受這個(gè)庫的具體處理方式影響了。

所以,你可以用其他語言進(jìn)行處理,核心思想還在,但規(guī)則變了。所以你會(huì)花費(fèi)一點(diǎn)點(diǎn)的思考。這樣其實(shí)對你自己有好處。

5.1、項(xiàng)目組織

按照領(lǐng)域驅(qū)動(dòng)的思想,將項(xiàng)目區(qū)分為四層:UI、Infra、Domain、Application

├─application
├─domain
│  ├─global
│  └─parse
├─infra
│  └─download
├─main
├─ui

infra層:


package download

import (
    "net/http"

    "errors"

    "github.com/PuerkitoBio/goquery"
)

var (
    ErrRequest  = errors.New("request err")
    ErrResponse = errors.New("response err")
)

func ResponseDownload(url string) (*goquery.Document, error) {
    request, err := http.NewRequest("GET", url, nil)
    if err != nil {
        return nil, ErrRequest
    }

    request.Header.Add("User-Agent", "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36")
    client := http.DefaultClient

    response, err := client.Do(request)
    if err != nil {
        return nil, ErrResponse
    }

    defer response.Body.Close()
    return goquery.NewDocumentFromReader(response.Body)
}

本質(zhì)是一個(gè)爬蟲,獲取網(wǎng)頁信息少不了這個(gè)函數(shù)。

domain 層:

package global

import "fmt"

const Version = "0.1.10"

var (
    BROWSERS_STATS_PAGE = "https://www.w3schools.com/browsers/default.asp"
    BROWSER_BASE_PAGE   = "http://useragentstring.com/pages/useragentstring.php?name=%s"
    CACHE_SERVER        = "http://d2g6u4gh6d9rq0.cloudfront.net/browsers/fake_useragent_%s.json"
)

var OVERRIDES = make(map[string]string)
var LOCALUSERAGENT = make(map[string][]string)

func init() {
    OVERRIDES = map[string]string{
        "Edge/IE": "Internet Explorer",
        "IE/Edge": "Internet Explorer",
    }
    CACHE_SERVER = fmt.Sprintf(CACHE_SERVER, Version)
}

全局參數(shù),主要是上面提到的網(wǎng)站信息。

  • BROWSERS_STATS_PAGE
  • BROWSER_BASE_PAGE
  • CACHE_SERVER
package parse

import (
    "errors"

    "github.com/PuerkitoBio/goquery"
    "github.com/tidwall/gjson"
)

var (
    ErrArray   = errors.New("array err")
    ErrInvalid = errors.New("invalid json")
)

func CloudFront(doc *goquery.Document, browserType string) ([]gjson.Result, error) {

    if !gjson.Valid(doc.Text()) {
        return nil, ErrInvalid
    }

    jsonResult := gjson.Parse(doc.Text())
    browserUserAgent := jsonResult.Get("browsers." + browserType)
    browserUserAgentOk := browserUserAgent.IsArray()
    if !browserUserAgentOk {
        return nil, ErrArray
    }
    return browserUserAgent.Array(), nil

}

cloudfront 網(wǎng)站獲取UserAgent

package parse

import (
    "github.com/PuerkitoBio/goquery"
)

var browserList = []string{
    "Internet Explorer",
    "Opera",
    "Chrome",
    "Safari",
    "FireFox",
}

func UserAgentCom(doc *goquery.Document) ([]string, error) {

    var newBrowserList = make([]string, 1)

    doc.Find("div#liste ul li").Each(func(i int, selection *goquery.Selection) {
        userAgent := selection.Find("a").Text()
        //fmt.Println(userAgent)
        newBrowserList = append(newBrowserList, userAgent)
    })
    return newBrowserList, nil

}

useragentstring.com 網(wǎng)站獲取 UserAgent

Application 層:

package application

import (
    "errors"
    "fake-user-agent-go-ng/domain/global"
    "fake-user-agent-go-ng/infra/download"
    "fmt"

    "fake-user-agent-go-ng/domain/parse"

    "math/rand"
    "time"

    "github.com/PuerkitoBio/goquery"
    "github.com/tidwall/gjson"
)

type FakeUserAgent struct {
    UserAgentStringOk bool
    CloudFrontNetOk   bool
    Cache             bool
}

var (
    ErrUserAgent = errors.New("user agent err")
)

func NewFakeUserAgent(UserAgentStringOk bool, CloudFrontNetOk bool, CacheOK bool) *FakeUserAgent {
    return &FakeUserAgent{
        UserAgentStringOk: UserAgentStringOk,
        CloudFrontNetOk:   CloudFrontNetOk,
        Cache:             CacheOK,
    }
}

func (F *FakeUserAgent) IE() string {
    return F.common("Internet+Explorer")

}

func (F *FakeUserAgent) InternetExplorer() string {
    return F.IE()
}

func (F *FakeUserAgent) Msie() string {
    return F.IE()
}

func (F *FakeUserAgent) Chrome() string {
    return F.common("Chrome")
}

func (F *FakeUserAgent) Google() string {
    return F.Chrome()
}

func (F *FakeUserAgent) Opera() string {
    return F.common("Opera")
}

func (F *FakeUserAgent) Safari() string {
    return F.common("Safari")
}

func (F *FakeUserAgent) FireFox() string {
    return F.common("Firefox")
}

func (F *FakeUserAgent) FF() string {
    return F.FireFox()
}

func (F *FakeUserAgent) Random() string {
    randomChoice := []string{
        "Chrome",
        "Firefox",
        "Safari",
        "Opera",
        "Internet+Explorer",
    }
    r := rand.NewSource(time.Now().UnixNano())
    random := rand.New(r)

    browserType := randomChoice[random.Intn(len(randomChoice))]
    return F.common(browserType)
}

func (F *FakeUserAgent) common(browserType string) string {
    r := rand.NewSource(time.Now().Unix())
    randomChoice := rand.New(r)
    if F.Cache {
        index := randomChoice.Intn(len(global.LOCALUSERAGENT[browserType]))
        return global.LOCALUSERAGENT[browserType][index]

    }

    var url string
    if F.UserAgentStringOk {
        url = fmt.Sprintf(global.BROWSER_BASE_PAGE, browserType)
    } else {
        url = global.CACHE_SERVER
    }

    var (
        doc *goquery.Document
        err error
    )

    doc, err = download.ResponseDownload(url)

    if err != nil {
        fmt.Println(ErrUserAgent)
        panic(ErrUserAgent)
    }
    var (
        userAgentList []string
    )

    if F.UserAgentStringOk {
        userAgentList, err = parse.UserAgentCom(doc)
        if err != nil {
            fmt.Println(ErrUserAgent)
            panic(ErrUserAgent)
        }
        return userAgentList[randomChoice.Intn(len(userAgentList))]
    }

    if F.CloudFrontNetOk {
        var userAgentResult []gjson.Result
        userAgentResult, err = parse.CloudFront(doc, browserType)
        if err != nil {
            fmt.Println(ErrUserAgent)
            panic(ErrUserAgent)
        }
        return userAgentResult[randomChoice.Intn(len(userAgentResult))].String()
    }
    return ""

}

主要是實(shí)現(xiàn)這幾個(gè)函數(shù):

  • Random 隨機(jī)得到一個(gè) UserAgent
  • IE/Msie/InternetExplorer 返回IE 瀏覽器UserAgent
  • FF/FireFox 返回 FireFox 瀏覽器UserAgent
  • Google/Chrome 返回 Chrome 瀏覽器UserAgent
  • Opera 返回 Opera 瀏覽器UserAgent

同時(shí)使用下面幾個(gè)參數(shù),決定從哪個(gè)網(wǎng)站抓取,還是使用本地緩存。

6. 開源

[圖片上傳失敗...(image-cb2b7f-1531152156667)]

歡迎使用唉。

本節(jié)完,再會(huì),我是謝偉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,211評論 3 119
  • 周末,六點(diǎn)三十分,許是習(xí)慣喚醒了我,許是米色半遮光窗簾外的晨曦喚醒了我,總之,再不復(fù)入眠。 起身躡手...
    夢1212閱讀 601評論 0 1
  • 親愛的兒子,今天周五,你依然沒有寫作業(yè),明明多說好了,為什么不做呢?也許是媽媽沒有說明白!晚上我們一起上了課,認(rèn)識(shí)...
    Cindycindycindy閱讀 132評論 0 0
  • 你如今穩(wěn)重利落溫婉可人的模樣你年少時(shí)曾設(shè)想過無數(shù)次,你只是不曾想真的有一天你成長成了這般模樣,你總是唏噓著過去說就...
    M丶八月閱讀 1,059評論 3 10

友情鏈接更多精彩內(nèi)容