Classe pour explorer récursivement un site, extraire liens, images et documents, et afficher les résultats dans TListBox/TTreeView avec compteurs et arrêt contrôlé.

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
unit LinkExplorerCore;
 
interface
 
uses
  System.Net.HttpClient, System.Net.HttpClientComponent, System.SysUtils,
  System.Classes,
  RegularExpressions, Generics.Collections, Vcl.Forms, Vcl.StdCtrls,
  Vcl.ComCtrls;
 
type
  // TLinkExplorer: classe principale pour explorer un site web,
  // détecter les liens, images et documents, et les afficher dans des
  // composants VCL (TListBox, TTreeView, TLabel).
  TLinkExplorer = class
  private
    // Dictionnaire de liens visités pour éviter les boucles et les doublons.
    VisitedLinks: TDictionary<string, Boolean>;
    // Compteur total de liens parcourus.
    TotalLinks: Integer;
    // Compteur total de fichiers (images + documents) ajoutés à l'arbre.
    FileCount: Integer;
    // Indicateur pour arrêter proprement l'exploration depuis l'UI.
    StopProcess: Boolean;
 
    // Récupère le contenu HTML d'une URL et le retourne comme string.
    function GetWebContent(const URL: string): string;
 
    // Extrait les URLs des balises <a href="..."> du HTML.
    // BaseURL est utilisé pour construire des URLs relatives.
    procedure ExtractLinks(const HTML: string; BaseURL: string;
      var LinkList: TStringList);
 
    // Extrait les sources médias (<img src=...> et liens de type pdf/html/css).
    // Remplit deux listes distinctes: ImageList et DocList.
    procedure ExtractMediaSources(const HTML: string; BaseURL: string;
      var ImageList, DocList: TStringList);
 
    // Ajoute les fichiers trouvés (images/documents) au TTreeView fourni.
    // Met aussi à jour le label indiquant le nombre total de fichiers.
    procedure AddFilesToTreeView(ImageList, DocList: TStringList;
      TreeView: TTreeView; FileLabel: TLabel);
 
    // Fonction récursive qui explore les liens jusqu'à une profondeur donnée.
    // Met à jour la ListBox (liste de pages parcourues), le TreeView (fichiers),
    // et les labels de compteurs.
    procedure ExploreLinksRecursive(const URL: string; ListBox: TListBox;
      TreeView: TTreeView; LabelCounter, FileLabel: TLabel; Depth: Integer);
  public
    // Démarre l'exploration depuis une URL, initialise les variables internes.
    // MaxDepth = profondeur maximale d'exploration (1 = seul la page initiale).
    procedure ExploreLinks(const URL: string; ListBox: TListBox;
      TreeView: TTreeView; LabelCounter, FileLabel: TLabel; MaxDepth: Integer);
 
    // Demande l'arrêt de l'exploration en cours (l'exploration vérifie StopProcess).
    procedure StopExploration;
  end;
 
implementation
 
uses
  System.IOUtils;
 
procedure TLinkExplorer.StopExploration;
begin
  // Indique au processus récursif qu'il doit s'arrêter dès que possible.
  StopProcess := True;
end;
 
function TLinkExplorer.GetWebContent(const URL: string): string;
var
  Client: TNetHTTPClient;
  Response: TStringStream;
begin
  // Initialise la valeur de retour vide par défaut.
  Result := '';
 
  // Création d'un client HTTP et d'un flux pour récupérer la réponse.
  Client := TNetHTTPClient.Create(nil);
  Response := TStringStream.Create;
  try
    try
      // Requête GET simple; le contenu renvoyé est placé dans Response.
      Client.Get(URL, Response);
      Result := Response.DataString;
    except
      // En cas d'erreur réseau ou autre, affiche un message dans la console.
      on E: Exception do
        Writeln('Erreur : ' + E.Message);
    end;
  finally
    // Libération des objets pour éviter les fuites mémoire.
    Client.Free;
    Response.Free;
  end;
end;
 
procedure TLinkExplorer.ExtractLinks(const HTML: string; BaseURL: string;
  var LinkList: TStringList);
var
  Regex: TRegEx;
  Match: TMatch;
  Link: string;
begin
  // Regex simple pour capturer href="..." dans les balises <a>.
  Regex := TRegEx.Create('<a\s+(?:[^>]*?\s+)?href="([^"]*)"', [roIgnoreCase]);
  Match := Regex.Match(HTML);
  while Match.Success do
  begin
    Link := Match.Groups[1].Value;
 
    // Normalise les URLs relatives en les transformant en URLs absolues basées sur BaseURL.
    if not Link.StartsWith('http') then
    begin
      if Link.StartsWith('/') then
        Link := BaseURL + Link
      else
        Link := BaseURL + '/' + Link;
    end;
 
    // Ajoute le lien s'il n'a pas encore été visité (évite doublons dans la file).
    if not VisitedLinks.ContainsKey(Link) then
      LinkList.Add(Link);
 
    Match := Match.NextMatch;
  end;
end;
 
// Enlève certains paramètres d'URL (ex : ?w=) pour normaliser les sources médias.
function RemoveURLParams(const URL: string): string;
begin
  Result := URL;
  if Pos('?w=', Result) > 0 then
    Result := Copy(Result, 1, Pos('?w=', Result) - 1);
end;
 
procedure TLinkExplorer.ExtractMediaSources(const HTML: string; BaseURL: string;
  var ImageList, DocList: TStringList);
var
  RegexImg, RegexDoc: TRegEx;
  Match: TMatch;
  Source: string;
begin
  // Regex pour trouver les balises <img src="..."> pointant vers des extensions d'image courantes.
  RegexImg := TRegEx.Create
    ('<img\s+[^>]*src="([^"]+\.(jpg|jpeg|png|gif|bmp|webp|svg))"',
    [roIgnoreCase]);
  Match := RegexImg.Match(HTML);
  while Match.Success do
  begin
    Source := RemoveURLParams(Match.Groups[1].Value);
 
    // Gère les sources relatives.
    if not Source.StartsWith('http') then
      Source := BaseURL + Source;
 
    // Vérifie la présence d'une extension avant d'ajouter.
    if TPath.GetExtension(Source) <> '' then
      ImageList.Add(Source);
 
    Match := Match.NextMatch;
  end;
 
  // Regex pour trouver les liens vers PDF, HTML, CSS (documents intéressants).
  RegexDoc := TRegEx.Create('<a\s+[^>]*href="([^"]+\.(pdf|html|css))"',
    [roIgnoreCase]);
  Match := RegexDoc.Match(HTML);
  while Match.Success do
  begin
    Source := RemoveURLParams(Match.Groups[1].Value);
 
    if not Source.StartsWith('http') then
      Source := BaseURL + Source;
 
    if TPath.GetExtension(Source) <> '' then
      DocList.Add(Source);
 
    Match := Match.NextMatch;
  end;
end;
 
// Recherche un nœud enfant direct d'un parent dont le texte correspond exactement.
// Retourne nil si non trouvé.
function FindTreeNode(ParentNode: TTreeNode; const Text: string): TTreeNode;
var
  Node: TTreeNode;
begin
  Result := nil;
  Node := ParentNode.getFirstChild;
  while Node <> nil do
  begin
    if Node.Text = Text then
    begin
      Result := Node;
      Exit;
    end;
    Node := Node.getNextSibling;
  end;
end;
 
procedure TLinkExplorer.AddFilesToTreeView(ImageList, DocList: TStringList;
  TreeView: TTreeView; FileLabel: TLabel);
var
  i: Integer;
  RootNode, ImagesNode, DocsNode: TTreeNode;
  AddedCount: Integer;
  // Compteurs locaux pour afficher le nombre par section dans le TreeView.
  ImageCount, DocCount: Integer;
  Node: TTreeNode;
begin
  AddedCount := 0;
 
  // Récupère le nœud racine ou le crée si absent.
  RootNode := TreeView.Items.GetFirstNode;
  if RootNode = nil then
    RootNode := TreeView.Items.Add(nil, 'Fichiers trouvés');
 
  // Cherche ou crée le sous-nœud "Images" (on compare seulement le préfixe pour conserver le compteur).
  ImagesNode := nil;
  Node := RootNode.getFirstChild;
  while Node <> nil do
  begin
    if SameText(Copy(Node.Text, 1, Length('Images')), 'Images') then
    begin
      ImagesNode := Node;
      Break;
    end;
    Node := Node.getNextSibling;
  end;
  if ImagesNode = nil then
    ImagesNode := TreeView.Items.AddChild(RootNode, 'Images');
 
  // Cherche ou crée le sous-nœud "Documents".
  DocsNode := nil;
  Node := RootNode.getFirstChild;
  while Node <> nil do
  begin
    if SameText(Copy(Node.Text, 1, Length('Documents')), 'Documents') then
    begin
      DocsNode := Node;
      Break;
    end;
    Node := Node.getNextSibling;
  end;
  if DocsNode = nil then
    DocsNode := TreeView.Items.AddChild(RootNode, 'Documents');
 
  // Ajoute les images sous le nœud "Images" en évitant les doublons.
  for i := 0 to ImageList.Count - 1 do
  begin
    if FindTreeNode(ImagesNode, ImageList[i]) = nil then
    begin
      TreeView.Items.AddChild(ImagesNode, ImageList[i]);
      Inc(AddedCount);
    end;
  end;
 
  // Ajoute les documents sous le nœud "Documents" en évitant les doublons.
  for i := 0 to DocList.Count - 1 do
  begin
    if FindTreeNode(DocsNode, DocList[i]) = nil then
    begin
      TreeView.Items.AddChild(DocsNode, DocList[i]);
      Inc(AddedCount);
    end;
  end;
 
  // Mise à jour du compteur global de fichiers et du label associé.
  if AddedCount > 0 then
  begin
    Inc(FileCount, AddedCount);
    if Assigned(FileLabel) then
      FileLabel.Caption := 'Fichiers trouvés : ' + IntToStr(FileCount);
  end;
 
  // Calcul du nombre d'enfants directs pour ImagesNode (pour afficher un compteur local).
  ImageCount := 0;
  Node := ImagesNode.getFirstChild;
  while Node <> nil do
  begin
    Inc(ImageCount);
    Node := Node.getNextSibling;
  end;
 
  // Calcul du nombre d'enfants directs pour DocsNode.
  DocCount := 0;
  Node := DocsNode.getFirstChild;
  while Node <> nil do
  begin
    Inc(DocCount);
    Node := Node.getNextSibling;
  end;
 
  // Met à jour le texte des nœuds pour inclure le compteur local.
  ImagesNode.Text := 'Images : ' + IntToStr(ImageCount);
  DocsNode.Text := 'Documents : ' + IntToStr(DocCount);
end;
 
procedure TLinkExplorer.ExploreLinksRecursive(const URL: string;
  ListBox: TListBox; TreeView: TTreeView; LabelCounter, FileLabel: TLabel;
  Depth: Integer);
var
  HTMLContent: string;
  Links, Images, Docs: TStringList;
  i: Integer;
begin
  // Vérifie le drapeau d'arrêt demandé par l'UI.
  if StopProcess then
    Exit;
 
  // Si la profondeur est atteinte, arrête la récursion.
  if Depth <= 0 then
    Exit;
 
  // Si l'URL a déjà été visitée, éviter de la revisiter.
  if VisitedLinks.ContainsKey(URL) then
    Exit;
 
  // Marque l'URL comme visitée et incrémente le compteur global.
  VisitedLinks.Add(URL, True);
  Inc(TotalLinks);
 
  // Permet au thread de l'interface utilisateur de traiter les messages
  // (utile si la méthode est appelée depuis le thread principal).
  Application.ProcessMessages;
  if Assigned(LabelCounter) then
    LabelCounter.Caption := 'Liens parcourus : ' + IntToStr(TotalLinks);
 
  // Récupère le contenu HTML de la page.
  HTMLContent := GetWebContent(URL);
  if HTMLContent = '' then
    Exit;
 
  // Création des listes locales pour stocker les résultats d'analyse.
  Links := TStringList.Create;
  Images := TStringList.Create;
  Docs := TStringList.Create;
  try
    // Extrait les liens et médias depuis le contenu HTML.
    ExtractLinks(HTMLContent, URL, Links);
    ExtractMediaSources(HTMLContent, URL, Images, Docs);
 
    // Ajoute l'URL parcourue à la ListBox pour visibilité.
    ListBox.Items.Add(URL);
 
    // Ajoute les images et documents trouvés dans le TreeView.
    AddFilesToTreeView(Images, Docs, TreeView, FileLabel);
 
    // Appel récursif sur chaque lien trouvé tant que la profondeur le permet.
    for i := 0 to Links.Count - 1 do
    begin
      if StopProcess then
        Exit;
      ExploreLinksRecursive(Links[i], ListBox, TreeView, LabelCounter,
        FileLabel, Depth - 1);
    end;
  finally
    // Libération des listes temporaires.
    Links.Free;
    Images.Free;
    Docs.Free;
  end;
end;
 
procedure TLinkExplorer.ExploreLinks(const URL: string; ListBox: TListBox;
  TreeView: TTreeView; LabelCounter, FileLabel: TLabel; MaxDepth: Integer);
begin
  // Initialise l'état interne de l'explorateur pour une nouvelle session.
  VisitedLinks := TDictionary<string, Boolean>.Create;
  TotalLinks := 0;
  FileCount := 0;
  StopProcess := False;
  try
    // Initialise le libellé des fichiers si fourni.
    if Assigned(FileLabel) then
      FileLabel.Caption := 'Fichiers trouvés : 0';
 
    // Lance la recherche récursive depuis l'URL racine.
    ExploreLinksRecursive(URL, ListBox, TreeView, LabelCounter, FileLabel,
      MaxDepth);
  finally
    // Libère le dictionnaire des liens visités.
    VisitedLinks.Free;
  end;
end;
 
end.
Nom : Capture d'écran 2025-10-11 173015.png
Affichages : 691
Taille : 82,1 Ko

Code Source : LinkExplorer.zip