Sed

從多個文件的 img html 標記中提取 URL

  • March 3, 2022

我有一個包含大量 .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

謝謝!

真的不想使用sedorgrep或任何基於正則表達式的提取方法。HTML 是結構化文本,因此您需要一個 HTML 解析器來可靠地從中提取數據。

大多數語言都有可用的 HTML 解析庫,包括 C、go、rust、java、python、php、perl 等等。還有一些命令行工具,例如xml_grep和 ,xmlstarlet用於在 shell 腳本中解析和處理 HTML/XHTML/XML 文件 - 它們很好,但根據我的經驗,它們往往更嚴格地要求輸入文件符合規範。特別是對於 HTML,這可能會導致問題 - 嚴格遵守規範對於現實世界的網站並不常見(這是一種禮貌的說法,即“HTML 文件通常是垃圾”)。解析庫往往對它們將處理的內容更加寬容,輕鬆處理將被更嚴格的工具拒絕的輸入。

順便說一句,還有一些工具,比如xml2html2用於將結構化文本轉換為面向行的格式,可以更容易地使用面向行的工具(如 grep、sed、cut 等)進行處理。

無論如何,使用 HTML 解析器不僅比使用正則表達式更可靠,而且通常也更容易。

這是一個使用 Perl 的HTML::TokeParser::Simple解析器的範例,它是HTML::Parser模組的簡單介面。如果您正在執行任何常見的 Linux 發行版,這些幾乎肯定會以軟體包的形式提供 - 例如在 Debian 和衍生產品上,它們被打包為libhtml-tokeparser-simple-perllibhtml-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 URL index.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 數據的更多資訊perldataperllol(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::DumpData::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 等等)執行的語言。

引用自:https://unix.stackexchange.com/questions/692502