HTML标签解析

15
如何使用DIHtmlParser从标签内解析Name: & Value文本?我尝试使用Clever Components的TCLHtmlParser进行操作,但失败了。第二个问题是,DIHtmlParser是否可以解析单个标签,例如循环遍历其子标签。对于这样一个简单的问题来说,这真是一个噩梦。
<div class="tvRow tvFirst hasLabel tvFirst" title="example1">
  <label class="tvLabel">Name:</label>
  <span class="tvValue">Value</span>
<div class="clear"></div></div>

<div class="tvRow tvFirst hasLabel tvFirst" title="example2">
  <label class="tvLabel">Name:</label>
  <span class="tvValue">Value</span>
<div class="clear"></div></div>

3
在我看来,一旦您拥有一个能够解析XHTML文档并将其无损转换为JSON文档的工具,实际上就不再需要使用JSON了。只需直接使用XHTML解释器生成的结构即可。此时,您不需要HTML转JSON转换器;您只需要一个允许您以编程方式访问文档的HTML库。 - Rob Kennedy
3
抱歉打破你的幻想,如果你要将HTML转换成JSON,那么你必须先解析HTML。只有在转换完成后,才能开始解析JSON。因此,将HTML转换为JSON,然后解析JSON的速度将始终比单独解析HTML的速度慢。 - Rob Kennedy
3
请使用HTML解析器。正则表达式无法用于解析HTML,即使是Jon Skeet等人也不能使用正则表达式来解析HTML等等。https://dev59.com/X3I-5IYBdhLWcg3wq6do#1732454 - David Heffernan
我认为你需要问的问题是,从Delphi中可以实际使用的最快的HTML解析器是什么。 - David Heffernan
3
@t0xic,提取标签本身是困难的部分:一旦你获得了标签,你可以很容易地提取属性。但不要自欺欺人,你真的不能用正则表达式解析HTML:你将很难猜测你看到的标签是否在注释中,或者它可能无效,因为它没有关闭。这就是为什么我说它取决于输入。如果你想从特定网站在特定时间抓取特定信息,你可以使用正则表达式。如果情况变得更加复杂,那么你就需要你的解析器。 - Cosmin Prund
显示剩余8条评论
3个回答

18
你可以使用 IHTMLDocument2 DOM从HTML中解析出所需的元素:
uses ActiveX, MSHTML;

const
  HTML =
  '<div class="tvRow tvFirst hasLabel tvFirst" title="example1">' +
  '<label class="tvLabel">Name:</label>' +
  '<span class="tvValue">Value</span>' +
  '<div class="clear"></div>' +
  '</div>';

procedure TForm1.Button1Click(Sender: TObject);
var
  doc: OleVariant;
  el: OleVariant;
  i: Integer;
begin
  doc := coHTMLDocument.Create as IHTMLDocument2;
  doc.write(HTML);
  doc.close;
  ShowMessage(doc.body.innerHTML);
  for i := 0 to doc.body.all.length - 1 do
  begin
    el := doc.body.all.item(i);
    if (el.tagName = 'LABEL') and (el.className = 'tvLabel') then
      ShowMessage(el.innerText);
    if (el.tagName = 'SPAN') and (el.className = 'tvValue') then
      ShowMessage(el.innerText);
  end;
end;

我今天发现了另一个非常好用的HTML解析器:htmlp(Delphi Dom HTML解析器和转换器)。显然,它不像IHTMLDocument2那样灵活,但很容易使用,快速,免费且支持旧版本的Delphi的Unicode。

示例用法:

uses HtmlParser, DomCore;

function GetDocBody(HtmlDoc: TDocument): TElement;
var
  i: integer;
  node: TNode;
begin
  Result := nil;
  for i := 0 to HtmlDoc.documentElement.childNodes.length - 1 do
  begin
    node := HtmlDoc.documentElement.childNodes.item(i);
    if node.nodeName = 'body' then
    begin
      Result := node as TElement;
      Break;
    end;
  end;
end;

procedure THTMLForm.Button2Click(Sender: TObject);
var
  HtmlParser: THtmlParser;
  HtmlDoc: TDocument;
  i: Integer;
  body, el: TElement;
  node: TNode;
begin
  HtmlParser := THtmlParser.Create;
  try
    HtmlDoc := HtmlParser.parseString(HTML);
    try
      body := GetDocBody(HtmlDoc);
      if Assigned(body) then
        for i := 0 to body.childNodes.length - 1 do
        begin
          node := body.childNodes.item(i);
          if (node is TElement) then
          begin
            el := node as TElement;
            if (el.tagName = 'div') and (el.GetAttribute('class') = 'tvRow tvFirst hasLabel tvFirst') then
            begin
              // iterate el.childNodes here...
              ShowMessage(IntToStr(el.childNodes.length));
            end;
          end;
        end;
    finally
      HtmlDoc.Free;
    end;
  finally
    HtmlParser.Free
  end;
end;

5
@T0xic:确保它在线程中运行,不要忘记先调用CoInitialize... - whosrdaddy

0
使用HTML解析器来处理您的HTML文件。
也许DIHtmlParser可以胜任这项工作。
正则表达式不是解析器,将HTML转换为JSON也不是一个明智的选择。

1
DIHtmlParser?这是一个非常复杂的解析器。和Clever Components的TCLHtmlParser完全不同。我尝试使用它,简直是一场噩梦。功能强大,但可用性却很糟糕。 - user1889268

0

我们也可以使用HTMLP解析器与THtmlFormatter以及OXml XPath解析的组合。

uses
  // Htmlp
  HtmlParser,
  DomCore,
  Formatter,
  // OXml
  OXmlPDOM,
  OXmlUtils;

function HtmlToXHtml(const Html: string): string;
var
  HtmlParser: THtmlParser;
  HtmlDoc: TDocument;
  Formatter: THtmlFormatter;
begin
  HtmlParser := THtmlParser.Create;
  try
    HtmlDoc := HtmlParser.ParseString(Html);
    try
      Formatter := THtmlFormatter.Create;
      try
        Result := Formatter.GetText(HtmlDoc);
      finally
        Formatter.Free;
      end;
    finally
      HtmlDoc.Free;
    end;
  finally
    HtmlParser.Free;
  end;
end;

type
  TCard = record
    Store: string;
    Quality: string;
    Quantity: string;
    Price: string;
  end;
  TCards = array of TCard;

function ParseCard(const Node: PXMLNode): TCard;
const
  StoreXPath = 'div[1]/ax';
  QualityXPath = 'div[3]';
  QuantityXPath = 'div[4]';
  PriceXPath = 'div[5]';
var
  CurrentNode: PXMLNode;
begin
  Result := Default(TCard);
  if Node.SelectNode(StoreXPath, CurrentNode) then
     Result.Store := CurrentNode.Text;
  if Node.SelectNode(QualityXPath, CurrentNode) then
     Result.Quality := CurrentNode.Text;
  if Node.SelectNode(QuantityXPath, CurrentNode) then
     Result.Quantity := CurrentNode.Text;
  if Node.SelectNode(PriceXPath, CurrentNode) then
     Result.Price := CurrentNode.Text;
end;

procedure THTMLForm.OpenButtonClick(Sender: TObject);
var
  Html: string;
  Xml: string;
  FXmlDocument: IXMLDocument;
  QueryNode: PXMLNode;
  XPath: string;
  NodeList: IXMLNodeList;
  i: Integer;
  Card: TCard;
begin
  Html := System.IOUtils.TFile.ReadAllText(FileNameEdit.Text, TEncoding.UTF8);
  Xml := HtmlToXHtml(Html);
  Memo.Lines.Text := Xml;

  // Parse with XPath
  FXMLDocument := CreateXMLDoc;
  FXMLDocument.WriterSettings.IndentType := itIndent;
  if not FXMLDocument.LoadFromXML(Xml) then
    raise Exception.Create('Source document is not valid');
  QueryNode := FXmlDocument.DocumentElement;
  XPath := '//div[@class="row pricetableline"]';
  NodeList := QueryNode.SelectNodes(XPath);
  for i := 0 to NodeList.Count -1 do
  begin
    Card := ParseCard(NodeList[i]);
    Memo.Lines.Text := Memo.Lines.Text + sLineBreak +
      Format('%0:s %1:s %2:s %3:s', [Card.Store, Card.Quality, Card.Quantity, Card.Price]);
  end;

  Memo.SelStart := 0;
  Memo.SelLength := 0;
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接