溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何使用Shell構(gòu)建多進(jìn)程的CommandlineFu爬蟲(chóng)

發(fā)布時(shí)間:2021-12-04 14:45:20 來(lái)源:億速云 閱讀:112 作者:小新 欄目:編程語(yǔ)言

小編給大家分享一下如何使用Shell構(gòu)建多進(jìn)程的CommandlineFu爬蟲(chóng),相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

CommandlineFu 是一個(gè)記錄腳本片段的網(wǎng)站,每個(gè)片段都有對(duì)應(yīng)的功能說(shuō)明和對(duì)應(yīng)的標(biāo)簽。我想要做的就是嘗試用 shell 寫(xiě)一個(gè)多進(jìn)程的爬蟲(chóng)把這些代碼片段記錄在一個(gè) org 文件中。

參數(shù)定義

這個(gè)腳本需要能夠通過(guò) -n 參數(shù)指定并發(fā)的爬蟲(chóng)數(shù)(默認(rèn)為 CPU 核的數(shù)量),還要能通過(guò) -f 指定保存的 org 文件路徑(默認(rèn)輸出到 stdout)。

#!/usr/bin/env bash proc_num=$(nproc)store_file=/dev/stdoutwhile getopts :n:f: OPT; do    case $OPT in        n|+n)            proc_num="$OPTARG"            ;;        f|+f)            store_file="$OPTARG"            ;;        *)            echo "usage: ${0##*/} [+-n proc_num] [+-f org_file} [--]"            exit 2    esacdoneshift $(( OPTIND - 1 ))OPTIND=1

解析命令瀏覽頁(yè)面

我們需要一個(gè)進(jìn)程從 CommandlineFu 的瀏覽列表中抽取各個(gè)腳本片段的 URL,這個(gè)進(jìn)程將抽取出來(lái)的 URL 存放到一個(gè)隊(duì)列中,再由各個(gè)爬蟲(chóng)進(jìn)程從進(jìn)程中讀取 URL 并從中抽取出對(duì)應(yīng)的代碼片段、描述說(shuō)明和標(biāo)簽信息寫(xiě)入 org 文件中。

這里就會(huì)遇到三個(gè)問(wèn)題:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 進(jìn)程之間通訊的隊(duì)列如何實(shí)現(xiàn)

  3. 如何從頁(yè)面中抽取出 URL、代碼片段、描述說(shuō)明、標(biāo)簽等信息

  4. 多進(jìn)程對(duì)同一文件進(jìn)行讀寫(xiě)時(shí)的亂序問(wèn)題

實(shí)現(xiàn)進(jìn)程之間的通訊隊(duì)列

這個(gè)問(wèn)題比較好解決,我們可以通過(guò)一個(gè)命名管道來(lái)實(shí)現(xiàn):

queue=$(mktemp --dry-run)mkfifo ${queue}exec 99<>${queue}trap "rm ${queue} 2>/dev/null" EXIT
從頁(yè)面中抽取想要的信息

從頁(yè)面中提取元素內(nèi)容主要有兩種方法:

  1. 鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)

  2. 對(duì)于簡(jiǎn)單的 HTML 頁(yè)面,我們可以通過(guò) sed、grep、awk 等工具通過(guò)正則表達(dá)式匹配的方式來(lái)從 HTML 中抽取信息。

  3. 通過(guò) html-xml-utils 工具集中的 hxselect 來(lái)根據(jù) CSS 選擇器提取相關(guān)元素。

這里我們使用 html-xml-utils 工具來(lái)提?。?/p>

function extract_views_from_browse_page(){    if [[ $# -eq 0 ]];then        local html=$(cat -)    else        local html="$*"    fi    echo ${html} |hxclean |hxselect -c -s "\n" "li.list-group-item > div:nth-child(1) > div:nth-child(1) > a:nth-child(1)::attr(href)"|sed 's@^@https://www.commandlinefu.com/@'} function extract_nextpage_from_browse_page(){    if [[ $# -eq 0 ]];then        local html=$(cat -)    else        local html="$*"    fi    echo ${html} |hxclean |hxselect -s "\n" "li.list-group-item:nth-child(26) > a"|grep '>'|hxselect -c "::attr(href)"|sed 's@^@https://www.commandlinefu.com/@'}

這里需要注意的是:hxselect 對(duì) HTML 解析時(shí)要求遵循嚴(yán)格的 XML 規(guī)范,因此在用 hxselect 解析之前需要先經(jīng)過(guò) hxclean 矯正。另外,為了防止 HTML 過(guò)大,超過(guò)參數(shù)列表長(zhǎng)度,這里允許通過(guò)管道的形式將  HTML 內(nèi)容傳入。

循環(huán)讀取下一頁(yè)的瀏覽頁(yè)面,不斷抽取代碼片段 URL 寫(xiě)入隊(duì)列

這里要解決的是上面提到的第三個(gè)問(wèn)題: 多進(jìn)程對(duì)管道進(jìn)行讀寫(xiě)時(shí)如何保障不出現(xiàn)亂序? 為此,我們需要在寫(xiě)入文件時(shí)對(duì)文件加鎖,然后在寫(xiě)完文件后對(duì)文件解鎖,在 shell 中我們可以使用 flock 來(lái)對(duì)文件進(jìn)行枷鎖。 關(guān)于 flock 的使用方法和注意事項(xiàng),請(qǐng)參見(jiàn)另一篇博文 Linux shell flock 文件鎖的用法及注意事項(xiàng)。

由于需要在 flock 子進(jìn)程中使用函數(shù) extract_views_from_browse_page,因此需要先導(dǎo)出該函數(shù):

export -f extract_views_from_browse_page

由于網(wǎng)絡(luò)問(wèn)題,使用 curl 獲取內(nèi)容可能失敗,需要重復(fù)獲?。?/p>

function fetch(){    local url="$1"    while ! curl -L ${url} 2>/dev/null;do        :    done}

collector 用來(lái)從種子 URL 中抓取待爬的 URL,寫(xiě)入管道文件中,寫(xiě)操作期間管道文件同時(shí)作為鎖文件:

function collector(){    url="$*"    while [[ -n ${url} ]];do        echo "從$url中抽取"        html=$(fetch "${url}")        echo "${html}"|flock ${queue} -c "extract_views_from_browse_page >${queue}"        url=$(echo "${html}"|extract_nextpage_from_browse_page)    done    # 讓后面解析代碼片段的爬蟲(chóng)進(jìn)程能夠正常退出,而不至于被阻塞.    for ((i=0;i<${proc_num};i++))    do        echo >${queue}    done}

這里要注意的是, 在找不到下一頁(yè) URL 后,我們用一個(gè) for 循環(huán)往隊(duì)列里寫(xiě)入了 =proc_num= 個(gè)空行,這一步的目的是讓后面解析代碼片段的爬蟲(chóng)進(jìn)程能夠正常退出,而不至于被阻塞。

解析腳本片段頁(yè)面

我們需要從腳本片段的頁(yè)面中抽取標(biāo)題、代碼片段、描述說(shuō)明以及標(biāo)簽信息,同時(shí)將這些內(nèi)容按 org 模式的格式寫(xiě)入存儲(chǔ)文件中。

  function view_page_handler()  {      local url="$1"      local html="$(fetch "${url}")"      # headline      local headline="$(echo ${html} |hxclean |hxselect -c -s "\n" ".col-md-8 > h2:nth-child(1)")"      # command      local command="$(echo ${html} |hxclean |hxselect -c -s "\n" ".col-md-8 > div:nth-child(2) > span:nth-child(2)"|pandoc -f html -t org)"      # description      local description="$(echo ${html} |hxclean |hxselect -c -s "\n" ".col-md-8 > div.description"|pandoc -f html -t org)"      # tags      local tags="$(echo ${html} |hxclean |hxselect -c -s ":" ".functions > a")"      if [[ -n "${tags}" ]];then          tags=":${tags}"      fi      # build org content      cat <<EOF |flock -x ${store_file} tee -a ${store_file}* ${headline}      ${tags} :PROPERTIES::URL:       ${url}:END: ${description}#+begin_src shell${command}#+end_src EOF  }

這里抽取信息的方法跟上面的類似,不過(guò)代碼片段和描述說(shuō)明中可能有一些 HTML 代碼,因此通過(guò) pandoc 將之轉(zhuǎn)換為 org 格式的內(nèi)容。

注意***輸出 org 模式的格式并寫(xiě)入存儲(chǔ)文件中的代碼不要寫(xiě)成下面這樣:

    flock -x ${store_file} cat <<EOF >${store_file}    * ${headline}\t\t ${tags}    ${description}    #+begin_src shell    ${command}    #+end_srcEOF

它的意思是使用 flock 對(duì) cat 命令進(jìn)行加鎖,再把 flock 整個(gè)命令的結(jié)果通過(guò)重定向輸出到存儲(chǔ)文件中,而重定向輸出的這個(gè)過(guò)程是沒(méi)有加鎖的。

spider 從管道文件中讀取待抓取的 URL,然后實(shí)施真正的抓取動(dòng)作。

function spider(){    while :    do        if ! url=$(flock ${queue} -c 'read -t 1 -u 99 url && echo $url')        then            sleep 1            continue        fi         if [[ -z "$url" ]];then            break        fi        view_page_handler ${url}    done}

這里要注意的是,為了防止發(fā)生死鎖,從管道中讀取 URL 時(shí)設(shè)置了超時(shí),當(dāng)出現(xiàn)超時(shí)就意味著生產(chǎn)進(jìn)程趕不上消費(fèi)進(jìn)程的消費(fèi)速度,因此消費(fèi)進(jìn)程休眠一秒后再次檢查隊(duì)列中的 URL。

組合起來(lái)

collector "https://www.commandlinefu.com/commands/browse" & for ((i=0;i<${proc_num};i++))do    spider &donewait

抓取其他網(wǎng)站

通過(guò)重新定義 extract_views_from_browse_page、 extract_nextpage_from-browse_page、 view_page_handler 這幾個(gè)函數(shù), 以及提供一個(gè)新的種子 URL,我們可以很容易將其改造成抓取其他網(wǎng)站的多進(jìn)程爬蟲(chóng)。

例如通過(guò)下面這段代碼,就可以用來(lái)爬取 xkcd 上的漫畫(huà):

function extract_views_from_browse_page(){    if [[ $# -eq 0 ]];then        local html=$(cat -)    else        local html="$*"    fi    max=$(echo "${html}"|hxclean |hxselect -c -s "\n" "#middleContainer"|grep "Permanent link to this comic" |awk -F "/" '{print $4}')    seq 1 ${max}|sed 's@^@https://xkcd.com/@'} function extract_nextpage_from_browse_page(){    echo ""} function view_page_handler(){    local url="$1"    local html="$(fetch "${url}/")"    local image="https:$(echo ${html} |hxclean |hxselect -c -s "\n" "#comic > img:nth-child(1)::attr(src)")"    echo ${image}    wget ${image}} collector "https://xkcd.com/" &

以上是“如何使用Shell構(gòu)建多進(jìn)程的CommandlineFu爬蟲(chóng)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI