go-15.png
15.png
大家好,我叫谢伟,是一名程序员。
在未来人人都是手艺人。
传统的手艺人在圈子内创造影响力,在互联网时代,个人影响力不仅仅限于圈子内,互联网创造无限可能性。
好,今天的主题:作品意识
作品很好理解,比如歌手发行唱片、发行单曲,作家写书等,前端工程师可能更容易出作品,比如,写一个优雅的网站,比如写一个优雅的工具,写一个实用的小程序,开发一个个人的APP等,这些都是作品。
后端人员,可以写库,虽然在和真实用户交互层面,后端工程师开发的工具大概只能在程序员内使用,或者有一定编程基础的人才能使用。
尽管不是每个人写的工具都能得到广泛的认可、使用。作为程序员,一定要有作品意识,什么意思?
对于后端开发者来说最终体现出来的作品便是“不断的创造轮子、或者重复造轮子”。
关于是否重复造轮子,知乎有广泛的讨论,我的观点是:想要提高个人的技能水平,需要不断的造轮子,水平低,造简单的轮子,水平渐渐提升,造更高级点的轮子。在过程参考优秀开源工具的实现、思想。
模仿是最简单的学习方式
在工作之余,我较长时间放在 Github 上。去发现一些好的项目,去参考一些好的效果。当然作为后端开发者,我不太会去看前端的项目,同样,作为Gopher ,我倾向于 关注 Go 项目,但如果是火热的 Python 项目,那我也会关注下。
随着关注点的越来越精细,我倾向于从我熟悉的东西入手,什么意思,为什么从熟悉的东西入手,因为我越来越发现,自信心是很重要的,如果你不能第一时间对一个项目提起兴趣和自信心,你可能没什么机会和这个项目产生化学反应。
近期在阅读 requests-html。
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
从导入的库来看,这个网页解析的库主要是整合了各种第三方库和内置库,这意味着,很少有直接从零开始造轮子,可以借助已经比较优秀的第三方库。
好,关于这个库的分析,我下次讲。
取其中的一个库来分析:fake_useragent
这是一个生成UserAgent 的库,可以指定浏览器的类型,也可以随机生成UserAgent。
我一不小心对它产生了兴趣。
网上一般的讲解如何随机生存UserAgent 的处理方法是,在本地缓存一个大的文件,随机从文件内取一个。当然这看上去不够极客唉。
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
用法十分简单,毕竟完成的功能就不是很复杂。
可以看出,其实是从网上抓取到的信息,在随机生成的。
那作者如何选择浏览器?只选择这几个浏览器的理由是?
image.png
可以查看当前最主流的浏览器的一些数据。
由有一些大家都知道的原因,这个网站的访问需要借助一些手段。
Sometimes, useragentstring.com or w3schools.com changes their html, or down, in such case fake-useragent uses CDN cloudfront fallback
意味着还可以从这个网站上获取UserAgent
本质是一个爬虫
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
核心代码是这两个函数。
数据来源:
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
你已经知道了这是个Python 库。好了,你也了解了这个库的核心代码和思想。
你下一步怎么做?
重新实现。
你可以选择 Python 实现,但是在你看源代码的过程中,你的思维应该已经受这个库的具体处理方式影响了。
所以,你可以用其他语言进行处理,核心思想还在,但规则变了。所以你会花费一点点的思考。这样其实对你自己有好处。
按照领域驱动的思想,将项目区分为四层: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)
}
本质是一个爬虫,获取网页信息少不了这个函数。
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)
}
全局参数,主要是上面提到的网站信息。
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 网站获取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 网站获取 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 ""
}
主要是实现这几个函数:
同时使用下面几个参数,决定从哪个网站抓取,还是使用本地缓存。