您好,登錄后才能下訂單哦!
小編給大家分享一下如何使用Shell構建多進程的CommandlineFu爬蟲,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
CommandlineFu 是一個記錄腳本片段的網站,每個片段都有對應的功能說明和對應的標簽。我想要做的就是嘗試用 shell 寫一個多進程的爬蟲把這些代碼片段記錄在一個 org 文件中。
這個腳本需要能夠通過 -n
參數指定并發的爬蟲數(默認為 CPU 核的數量),還要能通過 -f
指定保存的 org 文件路徑(默認輸出到 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
我們需要一個進程從 CommandlineFu 的瀏覽列表中抽取各個腳本片段的 URL,這個進程將抽取出來的 URL 存放到一個隊列中,再由各個爬蟲進程從進程中讀取 URL 并從中抽取出對應的代碼片段、描述說明和標簽信息寫入 org 文件中。
這里就會遇到三個問題:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
進程之間通訊的隊列如何實現
如何從頁面中抽取出 URL、代碼片段、描述說明、標簽等信息
多進程對同一文件進行讀寫時的亂序問題
這個問題比較好解決,我們可以通過一個命名管道來實現:
queue=$(mktemp --dry-run)mkfifo ${queue}exec 99<>${queue}trap "rm ${queue} 2>/dev/null" EXIT
從頁面中提取元素內容主要有兩種方法:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
對于簡單的 HTML 頁面,我們可以通過 sed
、grep
、awk
等工具通過正則表達式匹配的方式來從 HTML 中抽取信息。
通過 html-xml-utils 工具集中的 hxselect 來根據 CSS 選擇器提取相關元素。
這里我們使用 html-xml-utils 工具來提取:
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
對 HTML 解析時要求遵循嚴格的 XML 規范,因此在用 hxselect
解析之前需要先經過 hxclean
矯正。另外,為了防止 HTML 過大,超過參數列表長度,這里允許通過管道的形式將 HTML 內容傳入。
這里要解決的是上面提到的第三個問題: 多進程對管道進行讀寫時如何保障不出現亂序? 為此,我們需要在寫入文件時對文件加鎖,然后在寫完文件后對文件解鎖,在 shell 中我們可以使用 flock 來對文件進行枷鎖。 關于 flock
的使用方法和注意事項,請參見另一篇博文 Linux shell flock 文件鎖的用法及注意事項。
由于需要在 flock
子進程中使用函數 extract_views_from_browse_page
,因此需要先導出該函數:
export -f extract_views_from_browse_page
由于網絡問題,使用 curl
獲取內容可能失敗,需要重復獲取:
function fetch(){ local url="$1" while ! curl -L ${url} 2>/dev/null;do : done}
collector
用來從種子 URL 中抓取待爬的 URL,寫入管道文件中,寫操作期間管道文件同時作為鎖文件:
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 # 讓后面解析代碼片段的爬蟲進程能夠正常退出,而不至于被阻塞. for ((i=0;i<${proc_num};i++)) do echo >${queue} done}
這里要注意的是, 在找不到下一頁 URL 后,我們用一個 for 循環往隊列里寫入了 =proc_num=
個空行,這一步的目的是讓后面解析代碼片段的爬蟲進程能夠正常退出,而不至于被阻塞。
我們需要從腳本片段的頁面中抽取標題、代碼片段、描述說明以及標簽信息,同時將這些內容按 org 模式的格式寫入存儲文件中。
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 }
這里抽取信息的方法跟上面的類似,不過代碼片段和描述說明中可能有一些 HTML 代碼,因此通過 pandoc
將之轉換為 org 格式的內容。
注意***輸出 org 模式的格式并寫入存儲文件中的代碼不要寫成下面這樣:
flock -x ${store_file} cat <<EOF >${store_file} * ${headline}\t\t ${tags} ${description} #+begin_src shell ${command} #+end_srcEOF
它的意思是使用 flock
對 cat
命令進行加鎖,再把 flock
整個命令的結果通過重定向輸出到存儲文件中,而重定向輸出的這個過程是沒有加鎖的。
spider
從管道文件中讀取待抓取的 URL,然后實施真正的抓取動作。
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}
這里要注意的是,為了防止發生死鎖,從管道中讀取 URL 時設置了超時,當出現超時就意味著生產進程趕不上消費進程的消費速度,因此消費進程休眠一秒后再次檢查隊列中的 URL。
collector "https://www.commandlinefu.com/commands/browse" & for ((i=0;i<${proc_num};i++))do spider &donewait
通過重新定義 extract_views_from_browse_page
、 extract_nextpage_from-browse_page
、 view_page_handler
這幾個函數, 以及提供一個新的種子 URL,我們可以很容易將其改造成抓取其他網站的多進程爬蟲。
例如通過下面這段代碼,就可以用來爬取 xkcd 上的漫畫:
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構建多進程的CommandlineFu爬蟲”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。