爬取珍愛網後用戶信息展現

golang爬取珍愛網,爬到了3萬多用戶信息,並存到了elasticsearch中,以下圖,查詢到了3萬多用戶信息。javascript

image.png

先來看看最終效果:css

42.gif

利用到了go語言的html模板庫:html

執行模板渲染:前端

func (s SearchResultView) Render (w io.Writer, data model.SearchResult) error {
    return s.template.Execute(w, data)
}

model.SearchResult數據結構以下:java

type SearchResult struct {
    Hits int64
    Start int
    Query string
    PrevFrom int
    NextFrom int
    CurrentPage int
    TotalPage int64
    Items []interface{}
    //Items []engine.Item
}

<!DOCTYPE html>
<html xmlns:javascript="http://www.w3.org/1999/xhtml&...;>
<head>node

<title>Love Search</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="./css/style.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet"
      id="bootstrap-css">
<script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="./js/page.js"></script>

</head>
<body>python

<div id="demo">jquery

<div id="searchblank">
    <form method="get" class="form-inline">
        <div class="form-group">
            <input type="text" class="form-control" style="width: 500px" value="{{.Query}}" name="q">
            <button class="btn btn-default" type="submit" maxlength="100">搜索</button>
        </div>
    </form>
</div>
<h4 style="text-align: center">共爲你找到相關結果爲{{.Hits}}個。顯示從{{.Start}}起共{{len .Items}}個</h4>

<div id="customers" class="table-responsive-vertical shadow-z-1">
    <table id="table" class="table table-striped table-hover table-mc-indigo">
        <thead>
        <tr>
            <th>暱稱</th>
            <th>性別</th>
            <th>年齡</th>
            <th>身高</th>
            <th>體重</th>
            <th>收入</th>
            <th>學歷</th>
            <th>職位</th>
            <th>所在地</th>
            <th>星座</th>
            <th>購房狀況</th>
            <th>購車狀況</th>
        </tr>
        </thead>

        <tbody>
        {{range .Items}}
        <tr>
            <td><a href="{{.Url}}" target="_blank">{{.Payload.Name}}</a></td>
        {{with .Payload}}
            <td>{{.Gender}}</td>
            <td>{{.Age}}</td>
            <td>{{.Height}}CM</td>
            <td>{{.Weight}}KG</td>
            <td>{{.Income}}</td>
            <td>{{.Education}}</td>
            <td>{{.Occupation}}</td>
            <td>{{.Hukou}}</td>
            <td>{{.Xinzuo}}</td>
            <td>{{.House}}</td>
            <td>{{.Car}}</td>
        {{end}}
        </tr>
        {{else}}
        <tr>
            <td colspan="12">沒有找到相關用戶</td>
        </tr>
        {{end}}
        </tbody>
    </table>
    <div align="middle">
    {{if gt .CurrentPage 1}}
        <a href="search?q={{.Query}}&current={{Sub .CurrentPage 1}}">上一頁</a>
    {{end}}
    {{if lt .CurrentPage .TotalPage}}
        <a href="search?q={{.Query}}&current={{Add .CurrentPage 1}}">下一頁</a>
    {{end}}
        <span>共{{.TotalPage}}頁,當前第{{.CurrentPage}}頁</span>
    </div>
</div>

</div>
</body>
</html>git

其中用到了模板語法中的變量、函數、判斷、循環;

**模板函數的定義:**
上面模板代碼中的上一頁、下一頁的a標籤href裏用到了自定義模板函數Add和Sub分別用於獲取上一頁和下一頁的頁碼,傳到後臺(這裏並無用JavaScript去實現)。

html/template包中提供的功能有限,因此不少時候須要使用用戶定義的函數來輔助渲染頁面。下面講講模板函數如何使用。template包建立新的模板的時候,支持.Funcs方法來將自定義的函數集合導入到該模板中,後續經過該模板渲染的文件均支持直接調用這些函數。

**函數聲明**

// Funcs adds the elements of the argument map to the template's function map.
// It panics if a value in the map is not a function with appropriate return
// type. However, it is legal to overwrite elements of the map. The return
// value is the template, so calls can be chained.
func (t Template) Funcs(funcMap FuncMap) Template {github

t.text.Funcs(template.FuncMap(funcMap))
return t

}

Funcs方法就是用來建立咱們模板函數了,它須要一個FuncMap類型的參數:

// FuncMap is the type of the map defining the mapping from names to
// functions. Each function must have either a single return value, or two
// return values of which the second has type error. In that case, if the
// second (error) argument evaluates to non-nil during execution, execution
// terminates and Execute returns that error. FuncMap has the same base type
// as FuncMap in "text/template", copied here so clients need not import
// "text/template".
type FuncMap map[string]interface{}

**使用方法:**

在go代碼中定義兩個函數Add和Sub:

//減法,爲了在模板裏用減1
func Sub(a, b int) int {

return a - b

}

//加法,爲了在模板裏用加1
func Add(a, b int) int {

return a + b

}

**模板綁定模板函數:**

建立一個FuncMap類型的map,key是模板函數的名字,value是剛纔定義函數名。
將 FuncMap注入到模板中。

filename := "../view/template_test.html"

template, err := template.New(path.Base(filename)).Funcs(template.FuncMap{"Add": Add, "Sub": Sub}).ParseFiles(filename)

if err != nil {

t.Fatal(err)

}

**模板中如何使用:**

如上面html模板中上一頁處的:

{{Sub .CurrentPage 1}}

把渲染後的CurrentPage值加1

**注意:**

一、函數的注入,必需要在parseFiles以前,由於解析模板的時候,須要先把函數編譯註入。

二、Template object can have multiple templates in it and each one has a name. If you look at the implementation of ParseFiles, you see that it uses the filename as the template name inside of the template object. So, name your file the same as the template object, (probably not generally practical) or else use ExecuteTemplate instead of just Execute.

三、The name of the template is the bare filename of the template, not the complete path。若是模板名字寫錯了,執行的時候會出現:

error: template: 「…」 is an incomplete or empty template

尤爲是第三點,我今天就遇到了,模板名要用文件名,不能是帶路徑的名字,看如下代碼:

func TestTemplate3(t *testing.T) {

//filename := "crawler/frontend/view/template.html"
filename := "../view/template_test.html"

//file, _ := os.Open(filename)

t.Logf("baseName:%s\n", path.Base(filename))

tpl, err := template.New(filename).Funcs(template.FuncMap{"Add": Add, "Sub": Sub}).ParseFiles(filename)

if err != nil {
    t.Fatal(err)
}

page := common.SearchResult{}

page.Hits = 123
page.Start = 0
item := engine.Item {
    Url:  "http://album.zhenai.com/u/107194488",
    Type: "zhenai",
    Id:   "107194488",
    Payload: model.Profile{
        Name:       "霓裳",
        Age:        28,
        Height:     157,
        Marriage:   "未婚",
        Income:     "5001-8000元",
        Education:  "中專",
        Occupation: "程序媛",
        Gender:     "女",
        House:      "已購房",
        Car:        "已購車",
        Hukou:      "上海徐彙區",
        Xinzuo:    "水瓶座",
    },
}

page.CurrentPage = 1
page.TotalPage = 10
page.Items = append(page.Items, item)

afterHtml, err := os.Create("template_test1.html")

if err != nil {
    t.Fatal(err)
}

tpl.Execute(afterHtml, page)

}

這裏在template.New(filename)傳入的是文件名(上面定義時是帶路徑的文件名),致使執行完代碼後template_test1.html文件是空的,固然測試類的經過的,可是將此渲染到瀏覽器的時候,就會報:

template: 「…」 is an incomplete or empty template

因此,要使用文件的baseName,即:

tpl, err := template.New(path.Base(filename)).Funcs(template.FuncMap{"Add": Add, "Sub": Sub}).ParseFiles(filename)

這樣運行代碼後template_test1.html就是被渲染有內容的。

其餘語法:變量、判斷、循環用法比較簡單,我沒遇到問題;其餘語法,如:模板的嵌套,我目前沒用到,在此也不作贅述。

查詢遇到的問題:

由於查詢每頁顯示10條記錄,查詢第1000頁是正常的,當查詢大於等於1001頁的時候,會報以下錯誤:
![image.png](https://image-static.segmentfault.com/161/412/1614121155-5da89834e2f32_articlex)

用restclient工具調,錯誤更明顯了:

{
"error" : {

"root_cause" : [
  {
    "type" : "query_phase_execution_exception",
    "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10010]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
  }
],
"type" : "search_phase_execution_exception",
"reason" : "all shards failed",
"phase" : "query",
"grouped" : true,
"failed_shards" : [
  {
    "shard" : 0,
    "index" : "dating_profile",
    "node" : "bJhldvT6QeaRTvHmBKHT4Q",
    "reason" : {
      "type" : "query_phase_execution_exception",
      "reason" : "Result window is too large, from + size must be less than or equal to: [10000] but was [10010]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."
    }
  }
]

},
"status" : 500
}

問了谷哥後發現,是因爲ElasticSearch的默認 深度翻頁 機制的限制形成的。ES默認的分頁機制一個不足的地方是,好比有5010條數據,當你僅想取第5000到5010條數據的時候,ES也會將前5000條數據加載到內存當中,因此ES爲了不用戶的過大分頁請求形成ES服務所在機器內存溢出,默認對深度分頁的條數進行了限制,默認的最大條數是10000條,這是正是問題描述中當獲取第10000條數據的時候報Result window is too large異常的緣由。(由於頁面爲1001頁的時候後臺1001-1而後乘以10做爲from的值取查詢ES,而ES默認須要from+size要小於index.max_result_window: 最大窗口值)。

要解決這個問題,可使用下面的方式來改變ES默認深度分頁的index.max_result_window 最大窗口值

curl -XPUT http://127.0.0.1:9200/dating_profile/_settings -d '{ "index" : { "max_result_window" : 50000}}'

這裏的dating_profile爲index。

其中my_index爲要修改的index名,50000爲要調整的新的窗口數。將該窗口調整後,即可以解決沒法獲取到10000條後數據的問題。

## 注意事項

經過上述的方式解決了咱們的問題,但也引入了另外一個須要咱們注意的問題,窗口值調大了後,雖然請求到分頁的數據條數更多了,但它是用犧牲更多的服務器的內存、CPU資源來換取的。要考慮業務場景中過大的分頁請求,是否會形成集羣服務的**OutOfMemory**問題。在ES的官方文檔中對深度分頁也作了討論

> [https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html](https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html)
> 
> [https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html](https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html)

核心的觀點以下:

> Depending on the size of your documents, the number of shards, and the hardware you are using, paging 10,000 to 50,000 results (1,000 to 5,000 pages) deep should be perfectly doable. But with big-enough from values, the sorting process can become very heavy indeed, using vast amounts of CPU, memory, and bandwidth. For this reason, we strongly advise against deep paging.

這段觀點表述的意思是:根據文檔的大小,分片的數量以及使用的硬件,分頁10,000到50,000個結果(1,000到5,000頁)應該是徹底可行的。 可是,從價值觀上來看,使用大量的CPU,內存和帶寬,分類過程確實會變得很是重要。 爲此,**咱們強烈建議不要進行深度分頁**。

ES做爲一個搜索引擎,更適合的場景是使用它進行搜索,而不是大規模的結果遍歷。 大部分場景下,沒有必要獲得超過10000個結果項目, 例如,只返回前1000個結果。若是的確須要大量數據的遍歷展現,考慮是否能夠用其餘更合適的存儲。或者根據業務場景看可否用ElasticSearch的 **滾動API** (相似於迭代器,但有時間窗口概念)來替代。


到此展現的問題就解決了:

![頁數大於1001效果](https://image-static.segmentfault.com/346/820/3468209262-5da8983664f67_articlex)


項目代碼見:https://github.com/ll837448792/crawler

------------

* * *

**本公衆號**免費**提供csdn下載服務,海量IT學習資源,**若是你準備入IT坑,勵志成爲優秀的程序猿,那麼這些資源很適合你,包括但不限於java、go、python、springcloud、elk、嵌入式 、大數據、面試資料、前端 等資源。同時咱們組建了一個技術交流羣,裏面有不少大佬,會不定時分享技術文章,若是你想來一塊兒學習提升,能夠公衆號後臺回覆【**2**】,免費邀請加技術交流羣互相學習提升,會不按期分享編程IT相關資源。

* * *

掃碼關注,精彩內容第一時間推給你
相關文章
相關標籤/搜索