從多個文件的 img html 標記中提取 URL
我有一個包含大量 .html 文件的文件夾,這些文件中都有
<p>..</p>
html 標籤和<img...>
html 標籤我試圖僅將目前文件夾中所有文件的html標記內的 URL 提取到一個文件中**
<img...>
**output.txt
html 文件程式碼如下所示:
file1.html <p> text...</p> <img style='display' src="https://www.example.com/image1.jpg" width="100px" alt="image1"/> <p> text...</p> <p> text.. <a href="https://www.example.com/1">1</a> ... text....</p>
file2.html <p> text...</p> <img style='display' src="https://www.example.com/image2.jpg" width="100px" alt="image2"/> <p> text...</p> <p> text.. <a href="https://www.example.com/2">2</a> ... text....</p>
圖片 URL 路徑沒有特定的模式,它們可以在 URL 路徑中包含任何內容。
我的問題是這些 html 文件中的一些可能在 html 標記內還有其他 URL,所以我只需要從
<img ...>
html 標記中提取。理想情況下,我試圖將在
<img ...>
html 標記中找到的所有 URL 提取到 output.txt 中,如下所示:output.txt https://www.example.com/image1.jpg https://www.example.com/image2.jpg etc
以某種方式使用 sed 或 Regex 可能嗎?
我嘗試使用這個 sed 命令,但它似乎提取了所有 URL,無論它們在哪裡找到:
sed -n 's#.*\(https*://[^"]*\).*#\1#;p' file
謝謝!
您真的不想使用
sed
orgrep
或任何基於正則表達式的提取方法。HTML 是結構化文本,因此您需要一個 HTML 解析器來可靠地從中提取數據。大多數語言都有可用的 HTML 解析庫,包括 C、go、rust、java、python、php、perl 等等。還有一些命令行工具,例如
xml_grep
和 ,xmlstarlet
用於在 shell 腳本中解析和處理 HTML/XHTML/XML 文件 - 它們很好,但根據我的經驗,它們往往更嚴格地要求輸入文件符合規範。特別是對於 HTML,這可能會導致問題 - 嚴格遵守規範對於現實世界的網站並不常見(這是一種禮貌的說法,即“HTML 文件通常是垃圾”)。解析庫往往對它們將處理的內容更加寬容,輕鬆處理將被更嚴格的工具拒絕的輸入。順便說一句,還有一些工具,比如
xml2
和html2
用於將結構化文本轉換為面向行的格式,可以更容易地使用面向行的工具(如 grep、sed、cut 等)進行處理。無論如何,使用 HTML 解析器不僅比使用正則表達式更可靠,而且通常也更容易。
這是一個使用 Perl 的HTML::TokeParser::Simple解析器的範例,它是HTML::Parser模組的簡單介面。如果您正在執行任何常見的 Linux 發行版,這些幾乎肯定會以軟體包的形式提供 - 例如在 Debian 和衍生產品上,它們被打包為
libhtml-tokeparser-simple-perl
和libhtml-parser-perl
. 否則,可以使用cpan
.$ cat extract-img-urls.pl #!/usr/bin/perl use strict; use v5.16; # for fc (fold case) function use HTML::TokeParser::Simple; foreach my $f (@ARGV) { my $p = HTML::TokeParser::Simple->new(file => $f); while (my $token = $p->get_token) { next unless fc($token->[1]) eq fc('img'); print $token->[2]->{src} . "\n"; } };
將其保存到文件中,並使用 chmod 使其可執行 - 例如
chmod +x ./extract-img-urls.pl
使用您希望它處理的 HTML 文件列表作為參數執行它。您可以手動執行此操作,或者使用類似的東西
find
並-exec
為其提供文件名列表 - 例如,這表明我的 . 中只有兩個 IMG SRC URLindex.html
,它們都是相對的:$ find ~/public_html/ -maxdepth 1 -type f -name 'index.html' -exec ./extract-img-urls.pl {} + cas.jpg valid-html401.png
顯然,使用
find
匹配單個目錄中的單個文件是大材小用。這和 …. 一樣好用,./extract-img-urls.pl ~/public_html/index.html
但對於多個子目錄中的多個文件來說,這不是一個很好的例子。在您的情況下,您可能希望使用
-name '*.html'
(或-iname '*.html'
不區分大小寫的匹配)執行它。您可能還想刪除-maxdepth 1
謂詞,以便它還會在子目錄中找到 .html 文件。find ~/public_html/ -type f -iname '*.html' -exec ./extract-img-urls.pl {} +
最後,此範例假定 perl 腳本位於目前目錄中。如果沒有,請指定它的實際路徑而不是
./
…或者如果你把它放在你的 $PATH 中的某個地方(例如,創建一個目錄並將其添加到他們自己的腳本的 $PATH/usr/local/bin/
是相當普遍的做法)你可以~/bin/
無需指定路徑即可從任何地方執行它,就像使用 find、grep、sed、awk、perl 等常見程序一樣。IMG SRC url 通常是相對 URL。使用File::Basename模組(perl 包含的核心 perl 模組)中的
dirname()
函式(非常簡單地)為每個相對圖像文件名添加基本目錄:#!/usr/bin/perl use strict; use v5.16; # for fc (fold case) function use HTML::TokeParser::Simple; use File::Basename; foreach my $f (@ARGV) { my $base = dirname($f); my $p = HTML::TokeParser::Simple->new(file => $f); while (my $token = $p->get_token) { next unless fc($token->[1]) eq fc('img'); if ($token->[2]->{src} =~ m=^(https?|ftp)://|^/=i) { print $token->[2]->{src} . "\n"; } else { print $base . "/" . $token->[2]->{src} . "\n"; } } };
輸出:
$ find ~/public_html/ -maxdepth 1 -type f -name 'index.html' -exec ./extract-img-urls.pl {} + /home/cas/public_html/cas.jpg /home/cas/public_html/valid-html401.png
最後,這是一個列印它處理的每個文件的名稱的版本,在每個 IMG SRC url 之前插入一個製表符,並
\n
在每個文件之後插入一個換行符 ( )。如果您想使用其他工具或腳本處理輸出,則換行分隔符很有用。處理此類文本很容易,因為許多工具/語言都可以選擇以“段落模式”讀取數據。#!/usr/bin/perl use strict; use v5.16; # for fc (fold case) function use HTML::TokeParser::Simple; use File::Basename; foreach my $f (@ARGV) { print "$f\n"; my $base = dirname($f); my $p = HTML::TokeParser::Simple->new(file => $f); while (my $token = $p->get_token) { next unless fc($token->[1]) eq fc('img'); if ($token->[2]->{src} =~ m=^(https?|ftp)://|^/=i) { print "\t" . $token->[2]->{src} . "\n"; } else { print "\t" . $base . "/" . $token->[2]->{src} . "\n"; } }; print "\n"; };
在下面的範例輸出中,每個段落的第一行是文件名,其餘行是 IMG SRC URL,以製表符為前綴(製表符主要用於人類可讀性,但如果任何文件名包含換行符也很有用…這並不完美,因為如果文件名包含換行符後跟製表符,它仍然會有問題。這就是為什麼通常建議使用 NUL 字元作為文件名分隔符,它是唯一在 a 中無效的字元路徑/文件名)
$ ./extract-img-urls2.pl ~/public_html/index*.html /home/cas/public_html/index.html /home/cas/public_html/cas.jpg /home/cas/public_html/valid-html401.png /home/cas/public_html/index.old.html /home/cas/public_html/cas.jpg
perl 程式、變數引用、數組、對像等
tl;博士:這很神奇。
順便說一句,您可能想知道為什麼腳本使用
$token->[1]
和$token->[2]->{src}
. 那是因為我檢查了該get_token
方法返回的對象的結構,該對象HTML::TokeParser::Simple::Token::Tag::Start
的資料結構如下所示:[ 'S', 'img', { 'src' => 'valid-html401.png', 'width' => '88', 'alt' => 'Valid HTML 4.01 Transitional', 'height' => '31' }, [ 'src', 'alt', 'height', 'width' ], '<img src="valid-html401.png" alt="Valid HTML 4.01 Transitional" height="31" width="88">' ]
這是一個索引數組,包含一個作為元素 0 的字元串
'S'
(這意味著目前標記是一個開始標記,如<p>
…“E” 表示結束標記,如</p>
),一個包含作為元素 1 的 HTML 標記名稱的字元串'img'
,一個雜湊(associative array, in{...}
) 包含 HTML 標記的屬性名稱(鍵)和值作為元素 2,另一個索引數組或“列表”再次包含屬性名稱作為元素 4 (in[...]
),以及 img src 標記的實際 html 文本作為要素 5。這記錄在 中
man HTML::TokeParser
,它表示“S”類型令牌具有["S", $tag, $attr, $attrseq, $text]
. 的Argspec
部分man HTML::Parser
解釋了為什麼它包含一個雜湊 ( $ attr) AND an array containing the keys of the hash ( $ 屬性)。img
這是因為雜湊本質上是無序的,並且該數組用於記住在 HTML 原始碼的標記中看到鍵的原始順序。這是一種非常常見的技術,可以在不失去鍵順序的情況下獲得散列的便利。在 perl 程式碼中,
$token->[1]
指的是第二個元素(perl 數組從 0 開始,而不是 1),所以我們檢查它是否是img
(不區分大小寫)。如果是,那麼我們在:中列印src
散列的鍵。$token->[2]``$token->[2]->{src}
這
->
被稱為“箭頭運算符”,用於取消引用(訪問)資料結構中的值,非常類似於它在 C 或 C++ 中的使用方式。您可以通過閱讀手冊頁來了解有關 perl 數據的更多資訊
perldata
,perllol
(lol=“lists-of-lists”,AKA arrays-of-arrays,它們是包含其他數組的數組)和perldsc
(“Perl Data Structures Cookbook” )。另請參閱man perlref
和教程man perlreftut
。箭頭運算符還用於在perl物件導向程式中呼叫方法(即子常式)(請
man perlobj
參閱並將它返回的對象儲存在變數中)。$p = HTML::TokeParser::Simple->new(...)``$p``HTML::TokeParser::Simple``$p->get_token``$p``get_token()``$token
在開發/調試這樣的程式碼時,您可以使用Data::Dump或Data::Dumper之類的模組來漂亮地列印資料結構和對象……這通常比瀏覽文件更快且工作量更少。
Data:Dumper
是一個核心模組,包含在 perl 中。Data::Dump
不是,但很容易在 Debian 等上安裝apt-get install libdata-dump-perl
或使用cpan
. 它們都很好,但我通常更喜歡Data::Dump
.順便說一句,你不能做這樣的複雜資料結構
bash
——bash 支持數組和關聯數組(又名“雜湊”),但元素只能包含簡單的標量值,如單個字元串或數字。它們不能包含嵌套數組或雜湊。Bash(和其他一些類似 bourne 的 shell)有一些變數引用能力,但是如果你發現自己在使用它們,你真的應該使用更好的語言(幾乎是任何其他語言)——bash 不是數據處理的好語言,它是一種用於設置和協調其他程序(grep、sed、cut、perl、awk 等等)執行的語言。