从多个文件中的 img html 标签中提取 URL

从多个文件中的 img html 标签中提取 URL

我有一个包含大量 .html 文件的文件夹,这些文件都有<p>..</p>html 标签,并且<img...>所有文件都有 html 标签

我正在尝试提取仅 URL <img...>在html里面找到将当前文件夹中的所有文件标记为一个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

谢谢你!

答案1

真的不想使用sedgrep或任何基于正则表达式的提取方法。 HTML 是结构化文本,因此您需要一个 HTML 解析器来可靠地从中提取数据。

大多数语言都有可用的 HTML 解析库,包括 C、go、rust、java、python、php、perl 等等。还有一些命令行工具,例如xml_grepxmlstarlet用于在 shell 脚本中解析和处理 HTML/XHTML/XML 文件 - 它们很好,但根据我的经验,它们往往对要求输入文件符合规范更加严格。特别是对于 HTML,这可能会导致问题 - 严格遵守规范对于现实世界的网站来说并不常见(这是“HTML 文件通常是垃圾”的礼貌说法)。解析库往往对它们将处理的内容更加宽容,可以轻松处理会被更严格的工具拒绝的输入。

顺便说一句,还有一些工具,例如xml2html2用于将结构化文本转换为面向行的格式,可以更轻松地使用 grep、sed、cut 等面向行的工具进行处理。

不管怎样,使用 HTML 解析器不仅比使用正则表达式更可靠,而且通常也更容易。

这是一个使用 Perl 的示例HTML::TokeParser::简单解析器,一个简单的接口HTML::解析器模块。如果您正在运行任何常见的 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 中的某个位置(例如/usr/local/bin/,创建一个~/bin/目录并将其添加到自己的脚本的 $PATH 中是相当常见的做法),您可以从任何地方运行它,无需指定路径,就像使用常见程序(如 find、grep、sed、awk、perl 等)一样。


IMG SRC url 通常是相对 URL。使用dirname()以下函数(非常简单地)为每个相对图像文件名添加基目录文件::基本名称模块(包含在 perl 中的核心 perl 模块):

#!/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 字符作为文件名分隔符,它是唯一在文件中无效的字符。路径/文件名)

$ ./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">'
]

这是一个索引数组,其中包含一个字符串'S'(这意味着当前标记是一个开始标记,如<p>...“E”表示一个结束标记,如</p>)作为元素 0、一个包含 HTML 标记名称'img'作为元素 1 的字符串、一个哈希值(关联数组,在 中{...})包含 HTML 标签的属性名称(键)和值作为元素 2,另一个索引数组或“列表”再次包含属性名称作为元素 4(在 中[...]),以及 img src 标签的实际 html 文本作为元素 5。

这在 中进行了记录man HTML::TokeParser,其中表示“S”类型令牌具有["S", $tag, $attr, $attrseq, $text]。部分解释Argspecman HTML::Parser为什么它包含一个散列 ($attr) 和一个包含散列键的数组 ($attrseq)。这是因为哈希本质上是无序的,并且数组用于记住imgHTML 源代码中的标签中看到的键的原始顺序。这是一种非常常见的技术,可以在不丢失键顺序的情况下获得哈希的便利。

在 perl 代码中,$token->[1]指的是第二个元素(perl 数组从 0 开始,而不是 1),因此我们检查它是否是img(不区分大小写)。如果是,则我们在:src中打印哈希的键。$token->[2]$token->[2]->{src}

->被称为“箭头运算符”,用于取消引用(访问)数据结构中的值,与 C 或 C++ 中的使用方式非常相似。

您可以通过阅读(lol =“lists-of-lists”,AKA arrays-of-arrays,它们是包含其他数组的数组)和(“Perl Data Structures Cookbook”)的手册页perldata来了解有关 Perl 数据的更多信息)。另请参阅和教程。perllolperldscman perlrefman perlreftut

箭头运算符还用于在 Perl 面向对象编程中调用方法(即子例程)(请参阅man perlobj)...您可以在上面的代码中看到这样的示例,即$p = HTML::TokeParser::Simple->new(...)创建$p一个新HTML::TokeParser::Simple对象,并$p->get_token调用$pget_token()方法(并将其返回的对象存储在变量中$token)。

您可以使用类似的模块数据::转储或者数据::转储器在开发/调试这样的代码时漂亮地打印数据结构和对象......这通常比搜索文档更快且工作量更少。

Data:Dumper是一个核心模块,包含在 perl 中。 Data::Dump不是,但很容易安装在 Debian 等上,apt-get install libdata-dump-perl或者使用cpan.它们都很好,但我通常更喜欢Data::Dump.

顺便说一句,你不能在bashbash 支持数组和关联数组(又名“哈希”)中执行这样的复杂数据结构,但元素只能包含简单的标量值,例如单个字符串或数字。它们不能包含嵌套数组或哈希。 Bash(以及其他一些类似 bourne 的 shell)具有一些变量引用功能,但是如果您发现自己正在使用它们,那么您确实应该使用更好的语言(几乎是任何其他语言) - bash 不是一种适合数据处理的语言,它是一种用于设置和协调其他程序(grep、sed、cut、perl、awk 等等)执行的语言。

答案2

使用sed

$ sed -n '/<img/s/.*src=\([^ ]*\).*/\1/p' file1 file2
https://www.example.com/image1.jpg
https://www.example.com/image2.jpg

答案3

这是一个使用 XML 解析器的解决方案。

for file in *.html
do
    xmlstarlet format --html "$file" 2>/dev/null |
        xmlstarlet select --template --value-of '//img/@src' --nl
done >output.txt

其作用是将 HTML 强制转换为格式良好的 XML,然后依次src从每个标记中选择属性值。<img/>整个结果集写入output.txt

如果您的 HTML 文件已经是格式良好的 XML,您可以省去显式循环并将整个序列简化为单个命令

xmlstarlet select --template --value-of '//img/@src' --nl *.html >output.txt

或不太冗长

xmlstarlet sel -t -v '//img/@src' -n *.html >output.txt

答案4

这应该有效:

grep '^<img' *.html | grep -o 'http[^"]*' > output.txt

相关内容