這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
“白日依山盡,___”。下句自然填黃河入海流,那麼“日月忽其不淹兮,___,___,恐美人之遲暮”,中間兩句怎麼填呢?
最近工作中有個需求,就是1500道語文詩詞填空題沒有答案,現在需要給這些題目大爺們匹配它們對應的答案,好在題目資訊都很完整,指出了詩詞出處、作者資訊。自然想到到網上爬取對應文章資訊然後字串匹配答案。目前做完,效果還可以,基本上所有題目的答案都有了,現把操作流程記錄下,做個總結。
1. 文章資訊擷取
網上找了很久,目前發現百度漢語對古詩詞收錄的比較好,格式也比較規範,整個爬取過程還是比較簡單,瀏覽器分析網站,找到它們的搜尋介面:http://hanyu.baidu.com/hanyu/ajax/sugs只需要傳一個參數:mainkey,是一個urlencode格式的字串。介面會返回一個匹配列表,再用作者名稱對這個列表做篩選,詳細代碼如下:
baseUrl := "http://hanyu.baidu.com/hanyu/ajax/sugs?"client := &http.Client{}u, _ := url.Parse(baseUrl)q := u.Query()q.Set("mainkey", name)u.RawQuery = q.Encode() //添加Headerreq, _ := http.NewRequest("GET", u.String(), nil)req.Header.Add("User-Agent", `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36`)req.Header.Add("DNT", "1")req.Header.Add("Host", "hanyu.baidu.com")req.Header.Add("Accept-Language", "zh-CN,zh;q=0.8")req.Header.Add("Referer", "http://hanyu.baidu.com/shici/detail?pid=be520db056da43238035dc18bb1e1798&tn=sug_click") resp, errDo := client.Do(req)
拿到傳回值以後,篩選出對應的作者資訊。
//如果有多個搜尋結果,比對author是否正確respJson.ForEach(func(key, value gjson.Result) bool {//先看看有沒有display_namedisplayName := value.Get("display_name.0").String()sid := value.Get("sid.0").String()if len(displayName) == 0 {//不是這條記錄return true}//看看typetypeStr := value.Get("type.0").String() if typeStr == "poemline"{//取SourcedisplayName = value.Get("source_poem.0").String()sid = value.Get("source_poem_sid.0").String()}literatureAuthor := value.Get("literature_author.0").String()//author是否一致if literatureAuthor == author {searchResult.Sid = sidsearchResult.DisplayName = displayNamesearchResult.Author = literatureAuthorreturn false}return true // keep iterating})
SearchResult儲存了搜尋到的結果,根據sid拿到文章頁面,解析出其中的文章。
func GetContent(sid string) (content string, err error) {baseUrl := "http://hanyu.baidu.com/shici/detail"result := make([]string, 0, 0)client := &http.Client{}u, _ := url.Parse(baseUrl)q := u.Query()q.Set("pid", sid)u.RawQuery = q.Encode()req, _ := http.NewRequest("GET", u.String(), nil)req.Header.Add("User-Agent", `Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36`)req.Header.Add("DNT", "1")req.Header.Add("Host", "hanyu.baidu.com")req.Header.Add("Accept-Language", "zh-CN,zh;q=0.8")req.Header.Add("Referer", "http://hanyu.baidu.com/shici/detail?pid=be520db056da43238035dc18bb1e1798&tn=sug_click")resp, errDo := client.Do(req)if errDo != nil || resp.StatusCode != 200 {err = errors.New("無法串連百度漢語 " + errDo.Error())return}docm, errDoc := goquery.NewDocumentFromResponse(resp)if errDoc != nil {err = errors.New("解析Doc錯誤 " + errDoc.Error())return} //詩詞資訊都儲存在 body_p 的div中,可以通過PuerkitoBio/goquery庫擷取pSelect := docm.Find("#body_p")pSelect.Each(func(pos int, selection *goquery.Selection) {content := strings.TrimSpace(selection.Text())result = append(result, content)})content = strings.Join(result, "")return}
目前會爬取百度漢語、古詩詞兩個網站的資料,如果有更好的資料來源,只需要實現Spider介面,在MapSpiderManifest()方法中註冊即可。
type Spider interface {GetContent(SearchResult) (string, error)FindContent(string, string) (SearchResult, error)}func MapSpiderManifest() map[string]Spider {//初始化並且註冊所有的SpiderspiderMap := make(map[string]Spider)//百度baiduSpider := new(BaiduSpider)spiderMap["baiduSpider"] = baiduSpider//古詩文網gushiwenSpider := new(GushiwenSpider)spiderMap["gushiwenSpider"] = gushiwenSpiderreturn spiderMap}
2.詩詞句子尋找
古詩文默寫,以前上學的時候做的多了,把一句話摳出來,隨機選其中幾段話讓學生默寫。一般可以歸類為一下幾種模式:
- 開頭留空 :_,[_,...],何人不起故園情。
- 末尾留空:俱往矣,_,[_,...]。
- 中間留空:月出於東山之上,_,白露橫江,
不管是什麼樣的模式,就每個填空處來看,只有它前面或者後面有提示句,我們才能知道這個空的答案是什麼。也就是說,這樣的填空可以自主的找到答案,姑且稱之為自主空。而前後都沒有提示句的空,只能等待附近有自主空找到了答案,才能找到它本身的答案,用一個圖說明更加清晰:
圖中灰色的塊因為有提示句,所以可以通過步驟一種爬取下來的文章內容找到對應的答案,填入Blank中,具體的尋找演算法如下代碼所示:
//已知newFind的PreString,求BlankString和PostStringfunc makeWithPreContent(contentsSplit []string, newFind *Find) {for l := range contentsSplit {if isEqual(contentsSplit[l], newFind.PreString) && l < len(contentsSplit)-1 {newFind.BlankString = contentsSplit[l+1]if l < len(contentsSplit)-2 {newFind.PostString = contentsSplit[l+2]}newFind.BlankFinish = true}}}//已知newFind的PostString,求BlankString和PreStringfunc makeWithPostContent(contentsSplit []string, newFind *Find) {for l := range contentsSplit {if isEqual(contentsSplit[l], newFind.PostString) && l > 0 {newFind.BlankString = contentsSplit[l-1]if l-1 > 0 {newFind.PreString = contentsSplit[l-2]}newFind.BlankFinish = true}}}// 按標點符號分隔內容func SplitByPunctuation(s string) ([]string, []string) {regPunctuation, _ := regexp.Compile(`[,,。.??!!;;::]`)//匹配標點符號,儲存下來。 然後分割字串toPun := regPunctuation.FindAllString(s, -1)result := regPunctuation.Split(s, -1)if len(result[len(result)-1]) == 0 {result = result[:len(result)-1]}//去掉前後空格,去掉引號for i := range result {result[i] = strings.TrimSpace(result[i])regQuoting := regexp.MustCompile("[“”‘’']")result[i] = regQuoting.ReplaceAllString(result[i], "")}return result, toPun}
所有的自主塊都找到答案後,可以把每個自主塊看作一個雙向鏈表的表頭,我們所要做的就是遍曆每個雙向鏈表,把其中每個節點的答案都通過尋找演算法找出來。當下一個節點是NULL,或者下個節點是一個自主塊時,就停止遍曆,處理下一個雙向鏈表。這樣,無論給出的需要填空的內容多麼複雜,都可以順利的完整自動填寫工作。
3.效果
一些常用的文言文或詩詞:
1、 前赤壁賦
2、 離騷
項目地址:AncientPoetryFillBlank