【问题标题】:Delphi: Some tip to parse this html table?Delphi:解析这个 html 表的一些技巧?
【发布时间】:2014-07-19 08:13:32
【问题描述】:

有时我试图从这个 html 表中获取数据,我尝试了付费和免费的组件。我尝试进行一些编码,但也没有得到任何结果。我有一个直接为 ClientDataSet 抛出 html 表的类,但是对于这个表它不起作用。有人对如何获取此 html 表中的数据有任何提示吗?或者一种将其转换为 txt / xls / csv 或 xml 的方法?遵循表格的代码:

  WebBrowser1.Navigate('http://site2.aesa.pb.gov.br/aesa/monitoramentoPluviometria.do?metodo=listarMesesChuvasMensais');
  WebBrowser1.OleObject.Document.All.Tags('select').Item(0).Value:= '2013';
  WebBrowser1.OleObject.Document.All.Tags('select').Item(1).Value:= '7';
  WebBrowser1.OleObject.Document.All.Tags('input').Item(1).click;
  Memo1.Text:= WebBrowser1.OleObject.Document.All.Tags('table').Item(10).InnerHTML;
  Memo1.Lines.SaveToFile('table.html');

【问题讨论】:

  • 使用 html 解析器。没有网络浏览器控件的 neeb,因为您对显示网站不感兴趣。使用indy或类似的下载html,然后解析。
  • 你有 Indy 的例子吗?我尝试过使用 DIHtmlParser、CleverComponents、'SMImport'、我自己的代码......等等。我不太了解 html,这应该是问题所在。
  • 网络上的例子数不胜数。你可以很容易地为自己做这种研究。 Indy 当然不是 html 解析器。
  • 好吧,再过一周...Indy 是我最后的机会了。
  • 可能是@DavidHeffernan,我是一名环境工程师,我在教育期间学习了几种编程语言(Pascal、Matlab、R、fortran 等),没有 OOP 或网络语言​​.不到两个月我就致力于对象帕斯卡。

标签: html delphi html-parsing


【解决方案1】:

以下将从目标页面上的 HTML 表中提取数据 并将其加载到 ClientDataSet 中。

它相当冗长,也许证明正如大卫所说,德尔福 可能不是完成这项工作的最佳工具。

在我的 Form1 上,我有一个 TEdit,edValue,供我键入第一个中的值 HTML 表数据中的数据行。我用它作为在 HTML 文档。我敢说有更好的方法,但至少我的方法应该比关于嵌入表格的文档布局的硬编码假设更健壮,这种假设可能无法承受页面作者的更改。

一般来说,代码的工作原理是首先使用以下内容查找 HTML 表格单元格 我的edValue.Text,然后找到该单元格所属的表,然后 从表中填充 CDS 的字段和数据。

CDS 字段默认设置为 255 个字符;也许有一个规范 在网页上发布的数据,允许您对某些(如果不是全部)字段使用较小的值。它们都被假定为 ftString 类型,以避免代码因意外的单元格内容而窒息。

顺便说一句,底部是一个用于在本地保存 HTML 页面的实用功能,以 无需继续单击选择年+月的按钮。重新加载 保存文件中的 WebBrowser,只需使用文件名作为 URL 即可加载。

TForm1 = class(TForm)
[ ... ]
public
  { Public declarations }
  Doc : IHtmlDocument2;

procedure TForm1.btnFindValueClick(Sender: TObject);
var
  Table : IHTMLTable;
begin
  Doc := WebBrowser1.Document as IHTMLDocument2;
  Table := FindTableByCellValue(edValue.Text);
  Assert(Table <> Nil);
  LoadCDSFromHTMLTable(CDS, Table);
end;

procedure TForm1.LoadCDSFromHTMLTable(DestCDS : TClientDataSet; Table : IHTMLTable);
var
  I,
  J : Integer;
  vTable : OleVariant;
  iRow : IHTMLTableRow;
  FieldName,
  FieldValue : String;
  Field : TField;
const
  cMaxFieldSize = 255;
  scIDFieldName = 'ID';
begin
  //  Use OleVariant instead of IHTMLTable becuse it's less fiddly for doing what follows
  vTable := Table;
  Assert(not DestCDS.Active and (DestCDS.FieldCount = 0));

  //  First create an AutoInc field
  Field := TAutoIncField.Create(Self);
  Field.FieldName := scIDFieldName;
  Field.DataSet := DestCDS;


  // Next create CDS fields from the names in the cells in the first row of the table
  for I := 0 to (vTable.Rows.Item(0).Cells.Length - 1) do begin
    FieldName := vTable.Rows.Item(0).Cells.Item(I).InnerText;
    Field := TStringField.Create(Self);
    // At this point, we might want to clean up the FieldName by removing embedded spaces, etc
    Field.FieldName := FieldName;
    Field.Size := cMaxFieldSize;
    Field.DataSet := DestCDS;
  end;

  DestCDS.DisableControls;
  try
    DestCDS.IndexFieldNames := scIDFieldName;
    DestCDS.CreateDataSet;

    //  Next load the HTML table data into the CDS
    for I := 1 to (vTable.Rows.Length - 1) do begin
      DestCDS.Insert;
      for J := 0 to vTable.Rows.Item(0).Cells.Length - 1 do begin
        FieldValue := vTable.Rows.Item(I).Cells.Item(J).InnerText;
        // the J + 1 is because Fields[0] is the autoinc one
        DestCDS.Fields[J + 1].AsString := FieldValue;
      end;
      DestCDS.Post;
    end;
    DestCDS.First;
  finally
    DestCDS.EnableControls;
  end;
end;

function TForm1.FindTableCellByTagValue(Doc : IHtmlDocument2; const AValue : String) : IHTMLTableCell;
var
  All: IHTMLElementCollection;
  Value: String;
  I,
  Len: Integer;
  E: OleVariant;
  iE : IHTMLElement;
  iT : IHTMLTextElement;
  iC : IHTMLTableCell;
begin
  Result := Nil;
  All := Doc.All;
  if All = Nil then Exit;
  Len := All.Length;

  for I := 0 to Len - 1 do begin
    E := All.Item(I, varEmpty);
    iE := IDispatch(E) as IHTMLElement;
    if Supports(iE, IHTMLTableCell, iC) then begin
      Value := Trim(iE.Get_InnerText);
      if Pos(Trim(AValue), Value) = 1 then begin
        Result := iC;
        Break;
      end
    end
    else
      Continue;
  end;
end;

function TForm1.FindTableByCellValue(Value : String): IHTMLTable;
var
  Node : IHtmlElement;
  iTable : IHTMLTable;
  iCell : IHTMLTableCell;
begin
  Result := Nil;
  iCell := FindTableCellByTagValue(Doc, edValue.Text);
  if iCell = Nil then
    Exit;
  Node := IDispatch(iCell) as IHtmlElement;

  //  if we found a Node with the cell text we were looking for,
  //  we can now find the HTML table to which it belongs

  while Node <> Nil do begin
    Node := Node.parentElement;
    if Supports(Node, IHTMLTable, iTable) then begin
      Result := iTable;
      Break;
    end;
  end;
end;

procedure TForm1.SaveFileLocally(const FileName : String);
var
  PFile: IPersistFile;  // declared in ActiveX unit
begin
  PFile := Doc as IPersistFile;
  PFile.Save(StringToOleStr(FileName), False);
end;

【讨论】:

  • 你好,哇!非常感谢。盖伊刚刚发布了我的答案,当我看到你的页面更新时,哇,你的工作真棒!和我的比不了,再次感谢。实际上这项工作很繁重,但我必须在 Delphi 中完成,我现在正在尝试使用 MSHTML,这段代码会有很大帮助。我还有很多工作要做,因为我想每年和每个月循环浏览每一页(有五页),然后放在 CD 上......
  • ...在您和上述 cmets 同事的帮助下,我更加开阔了思路,克服了挑战。再次感谢繁重的代码,它将对我和其他人(需要示例)有很大帮助。
【解决方案2】:

经过一段时间的研究,我终于从 html 表中提取数据。为了简化,我可以直接从 html 表中提取数据,而无需“解析”标签“表”和“项目”11,“项目”10 具有相同的数据,但在一个单元格中。所以我做了什么,我把表格的每个元素都用html和StringGrid填充了一个,然后找到了一种通过ClientDataSet直接填充dbgrid的方法。我将发布代码(单元)作为示例,为此您需要有人。我要感谢所有在 cmets 中帮助过我的人。随着更多研究的发现,执行此过程的最佳方法是使用 MSHTML。

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.OleCtrls, SHDocVw, Vcl.StdCtrls,
  Vcl.Grids, Vcl.DBGrids, Data.DB, Datasnap.DBClient;

type
  TForm1 = class(TForm)
    WebBrowser1: TWebBrowser;
    DBGrid1: TDBGrid;
    StringGrid1: TStringGrid;
    Button1: TButton;
    Button2: TButton;
    ClientDataSet1: TClientDataSet;
    DataSource1: TDataSource;
    ClientDataSet1MunicípioPosto: TStringField;
    ClientDataSet1TotalMensalmm: TStringField;
    ClientDataSet1ClimatologiaMensalmm: TStringField;
    ClientDataSet1Desviomm: TStringField;
    ClientDataSet1Desvio: TStringField;
    ClientDataSet1id: TAutoIncField;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
 irow, jcol: Integer;
 ovTable: OleVariant;
begin

 ovTable := WebBrowser1.OleObject.Document.all.tags('table').item(11);
 ShowMessage('Number of Rows: '+IntToStr(ovTable.Rows.Length));
 ShowMessage('Number of Cols: '+IntToStr(ovTable.Rows.Item(0).Cells.Length));
 StringGrid1.RowCount:= ovTable.Rows.Length+1;
 StringGrid1.ColCount:= ovTable.Rows.Item(0).Cells.Length+1;
 for irow := 0 to (ovTable.Rows.Length - 1) do
 begin
   for jcol := 0 to (ovTable.Rows.Item(irow).Cells.Length - 1) do
   begin
     StringGrid1.Cells[jcol+1, irow+1] := ovTable.Rows.Item(irow).Cells.Item(jcol).InnerText;
   end;
 end;
end;

procedure TForm1.Button2Click(Sender: TObject);

var
iRow : Integer;
iCol : Integer;
ovTable: OleVariant;

begin
  ovTable := WebBrowser1.OleObject.Document.all.tags('table').item(11);
  for iRow := 1 to (ovTable.Rows.Length - 1) do
    begin
      ClientDataSet1.Open;
      ClientDataSet1.insert;
      for iCol := 0 to (ovTable.Rows.Item(iRow).Cells.Length - 1) do
      begin
      ClientDataSet1.FieldByname('Município/Posto').AsString:=ovTable.Rows.Item(iRow).Cells.Item(0).InnerText;
      ClientDataSet1.FieldByname('Total Mensal (mm)').AsString:=ovTable.Rows.Item(iRow).Cells.Item(1).InnerText;
      ClientDataSet1.FieldByname('Climatologia Mensal (mm)').AsString:=ovTable.Rows.Item(iRow).Cells.Item(2).InnerText;
      ClientDataSet1.FieldByname('Desvio (mm)').AsString:=ovTable.Rows.Item(iRow).Cells.Item(3).InnerText;
      ClientDataSet1.FieldByname('Desvio (%)').AsString:=ovTable.Rows.Item(iRow).Cells.Item(4).InnerText;

      end;
      ClientDataSet1.Post;
      ClientDataSet1.IndexFieldNames:= 'id';
      ClientDataSet1.First;
  end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  WebBrowser1.Navigate('C:\htmlwiththetable.html');
end;

end

.

【讨论】:

  • 很高兴您取得了进展。只是一件小事:重新安排您的 FormClose 以便 Application.Terminate 是您调用的最后一件事 - 尽管最好完全删除它,因为如果 Form1 是您项目中的主表单,关闭它无论如何都会终止您的应用程序.
  • 这是真的。我在 FormClose 上写了一个代码,然后放手,我会解决这个问题,谢谢。
猜你喜欢
  • 2011-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多