From 201d052ee00b45ba1937e314a65219611a9220a7 Mon Sep 17 00:00:00 2001 From: Nicolas Pravdin Date: Thu, 9 Apr 2015 14:49:48 +0800 Subject: [PATCH] * fixed: multiline exceptions + documentation + unit tests --- README.md | 2 +- Test/TestuWebScraper.pas | 103 +++++++ Test/URL2TitleTests.dpr | 29 ++ Test/URL2TitleTests.dproj | 535 +++++++++++++++++++++++++++++++++++++ Test/URL2TitleTests.res | Bin 0 -> 96 bytes Test/Win32/Debug/dunit.ini | 26 ++ URL2Title.deployproj | 9 + URL2Title.dpr | 12 +- URL2Title.dproj | 142 ++-------- URL2Title.res | Bin 62452 -> 32 bytes URL2TitlePG.groupproj | 48 ++++ uMain.pas | 169 ++---------- uUserAgent.pas | 129 +++++++++ uWebScraper.pas | 230 ++++++++++++++++ 14 files changed, 1162 insertions(+), 272 deletions(-) create mode 100644 Test/TestuWebScraper.pas create mode 100644 Test/URL2TitleTests.dpr create mode 100644 Test/URL2TitleTests.dproj create mode 100644 Test/URL2TitleTests.res create mode 100644 Test/Win32/Debug/dunit.ini create mode 100644 URL2TitlePG.groupproj create mode 100644 uUserAgent.pas create mode 100644 uWebScraper.pas diff --git a/README.md b/README.md index 9fa6077..f99c117 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Get list of page titles from list of URLs ### Compilation guide -In order to compile this sources on Windows, you need to install the Embarcadero RAD Studio XE7 Update 1 environment. +In order to compile this sources on Windows, you need to install the Embarcadero RAD Studio XE8 environment. ### Known bugs diff --git a/Test/TestuWebScraper.pas b/Test/TestuWebScraper.pas new file mode 100644 index 0000000..8f936ac --- /dev/null +++ b/Test/TestuWebScraper.pas @@ -0,0 +1,103 @@ +unit TestuWebScraper; +{ + + Delphi DUnit Test Case + ---------------------- + This unit contains a skeleton test case class generated by the Test Case Wizard. + Modify the generated code to correctly setup and call the methods from the unit + being tested. + +} + +interface + +uses + TestFramework, System.Variants, IdSSLOpenSSL, uWebScraper, System.Threading, + Generics.Collections, IdHTTP, IdIOHandlerStack, System.SysUtils, IdTCPConnection, + System.Generics.Defaults, IdTCPClient, System.UITypes, System.Classes, + IdIOHandlerSocket, IdComponent, System.Types, IdBaseComponent, IdSSL, IdIOHandler, + System.Math, System.NetEncoding, System.RegularExpressions; + +type + // Test methods for class TWebScraper + + TestTWebScraper = class(TTestCase) + strict private + FWebScraper: TWebScraper; + public + procedure SetUp; override; + procedure TearDown; override; + published + procedure TestGetTitlesFromURLs; + end; + +const + URLsToTest: TArray = [ + 'http://stackoverflow.com/', // HTTP with EN title + 'http://habrahabr.ru/interesting/', // HTTP with RU title + 'http://www.yandex.ru/', // HTTP with RU title + + 'https://encrypted.google.com/', // HTTPS with EN title + 'https://www.youtube.com/', // HTTPS with EN title + 'https://www.python.org/', // HTTPS with EN title + 'https://vk.com/', // HTTPS with RU title + + 'http://☃.net/', // punycode domain + 'http://㯙㯜㯙㯟.net/', // punycode domain + 'http://💩.la', // punycode domain + + 'http://википедия.орг.рф/', // international domain + 'http://кто.рф/', // international domain + + 'http://vk.com/', // HTTP -> HTTPS redirect + 'http://гугл.рф/', // HTTP -> HTTPS redirect + + 'http://www.mindlis.ru/', // timeout + 'http://СИБИРСКИЙ-ФЕДЕРАЛЬНЫЙ-ОКРУГ.РФ', // timeout + 'https://naydenov.tk/', // error: no HTTPS + 'http://youtu.be/404.', // 404 + 'http://stackoverflow.com/404' // 404 + ]; + +implementation + +procedure TestTWebScraper.SetUp; +begin + FWebScraper := TWebScraper.Create; +end; + +procedure TestTWebScraper.TearDown; +begin + FWebScraper.Free; + FWebScraper := nil; +end; + +procedure TestTWebScraper.TestGetTitlesFromURLs; +var + URLStrings: TStringList; + ResultStrings: TStringList; + s: string; +begin + URLStrings := TStringList.Create; + for s in URLsToTest do + URLStrings.Add(s); + try + ResultStrings := TStringList.Create; + try + FWebScraper.GetTitlesFromURLs(URLStrings, ResultStrings); + // Validate method results + (URLStrings.Count = ResultStrings.Count); + finally + FreeAndNil(ResultStrings); + end; + finally + FreeAndNil(URLStrings); + end; +end; + +initialization + +// Register any test cases with the test runner +RegisterTest(TestTWebScraper.Suite); + +end. diff --git a/Test/URL2TitleTests.dpr b/Test/URL2TitleTests.dpr new file mode 100644 index 0000000..44253bb --- /dev/null +++ b/Test/URL2TitleTests.dpr @@ -0,0 +1,29 @@ +program URL2TitleTests; +{ + + Delphi DUnit Test Project + ------------------------- + This project contains the DUnit test framework and the GUI/Console test runners. + Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options + to use the console test runner. Otherwise the GUI test runner will be used by + default. + +} + +{$IFDEF CONSOLE_TESTRUNNER} +{$APPTYPE CONSOLE} +{$ENDIF} + +uses + TestuWebScraper in 'TestuWebScraper.pas', + uWebScraper in '..\uWebScraper.pas', + uUserAgent in '..\uUserAgent.pas', + DUnitTestRunner; + +{$R *.RES} + +begin + ReportMemoryLeaksOnShutdown := true; + DUnitTestRunner.RunRegisteredTests; + +end. diff --git a/Test/URL2TitleTests.dproj b/Test/URL2TitleTests.dproj new file mode 100644 index 0000000..cf73ba3 --- /dev/null +++ b/Test/URL2TitleTests.dproj @@ -0,0 +1,535 @@ + + + {EFA3D923-267E-4D33-B14D-4AE7629C5030} + 17.2 + None + True + Debug + Win32 + 1 + Console + URL2TitleTests.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + _CONSOLE_TESTRUNNER;$(DCC_Define) + $(BDS)\Source\DUnit\src;$(DCC_UnitSearchPath) + System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace) + . + .\$(Platform)\$(Config) + false + false + false + false + false + + + FireDACTDataDriver;FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;FireDACPgDriver;fmx;IndySystem;tethering;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;DataSnapProviderClient;DbxCommonDriver;dbxcds;fmxFireDAC;DBXOracleDriver;CustomIPTransport;dsnap;IndyIPServer;fmxase;IndyCore;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;DataSnapFireDAC;FireDACDBXDriver;soapserver;inetdbxpress;dsnapxml;FireDACASADriver;bindcompfmx;FireDACODBCDriver;RESTBackendComponents;emsclientfiredac;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;ibmonitor;xmlrtl;DataSnapNativeClient;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;bindcompdbx;soaprtl;bindengine;FMXTee;emsclient;FireDAC;DBXInformixDriver;FireDACMSSQLDriver;DataSnapServerMidas;DBXFirebirdDriver;inet;fmxobj;FireDACMySQLDriver;soapmidas;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) + + + FireDACTDataDriver;FireDACSqliteDriver;FireDACDSDriver;frxTee22;DBXSqliteDriver;FireDACPgDriver;fmx;IndySystem;TeeDB;tethering;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;DataSnapProviderClient;DBXSybaseASEDriver;frxe22;DbxCommonDriver;vclimg;DUnitXRuntime;dbxcds;DatasnapConnectorsFreePascal;appanalytics;vcldb;vcldsnap;fmxFireDAC;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;DBXMSSQLDriver;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;CodeSiteExpressPkg;DataSnapFireDAC;FireDACDBXDriver;soapserver;inetdbxpress;dsnapxml;FireDACInfxDriver;FireDACDb2Driver;adortl;FireDACASADriver;frx22;bindcompfmx;FireDACODBCDriver;RESTBackendComponents;emsclientfiredac;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;Tee;DBXOdbcDriver;ibmonitor;vclFireDAC;xmlrtl;DataSnapNativeClient;svnui;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;bindcompdbx;soaprtl;bindengine;vclactnband;FMXTee;TeeUI;bindcompvcl;vclie;frxDB22;FireDACADSDriver;vcltouch;emsclient;VclSmp;FireDAC;DBXInformixDriver;FireDACMSSQLDriver;Intraweb;VCLRESTComponents;DataSnapConnectors;DataSnapServerMidas;dsnapcon;DBXFirebirdDriver;inet;fmxobj;FireDACMySQLDriver;soapmidas;vclx;svn;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;FireDACMSAccDriver;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) + 1033 + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments= + + + FireDACTDataDriver;FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;FireDACPgDriver;fmx;IndySystem;TeeDB;tethering;vclib;DBXInterBaseDriver;DataSnapClient;DataSnapServer;DataSnapCommon;DataSnapProviderClient;DBXSybaseASEDriver;DbxCommonDriver;vclimg;dbxcds;DatasnapConnectorsFreePascal;appanalytics;vcldb;vcldsnap;fmxFireDAC;DBXDb2Driver;DBXOracleDriver;CustomIPTransport;vclribbon;dsnap;IndyIPServer;fmxase;vcl;IndyCore;DBXMSSQLDriver;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;DataSnapFireDAC;FireDACDBXDriver;soapserver;inetdbxpress;dsnapxml;FireDACInfxDriver;FireDACDb2Driver;adortl;FireDACASADriver;bindcompfmx;FireDACODBCDriver;RESTBackendComponents;emsclientfiredac;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;inetdb;Tee;DBXOdbcDriver;ibmonitor;vclFireDAC;xmlrtl;DataSnapNativeClient;ibxpress;IndyProtocols;DBXMySQLDriver;FireDACCommonDriver;bindcompdbx;soaprtl;bindengine;vclactnband;FMXTee;TeeUI;bindcompvcl;vclie;FireDACADSDriver;vcltouch;emsclient;VclSmp;FireDAC;DBXInformixDriver;FireDACMSSQLDriver;Intraweb;VCLRESTComponents;DataSnapConnectors;DataSnapServerMidas;dsnapcon;DBXFirebirdDriver;inet;fmxobj;FireDACMySQLDriver;soapmidas;vclx;DBXSybaseASADriver;FireDACOracleDriver;fmxdae;RESTComponents;FireDACMSAccDriver;dbexpress;DataSnapIndy10ServerTransport;IndyIPClient;$(DCC_UsePackage) + + + DEBUG;$(DCC_Define) + true + false + true + true + true + + + false + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + + MainSource + + +
$R *.RES
+
+ + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + +
+ + Delphi.Personality.12 + Application + + + + URL2TitleTests.dpr + + + + + + URL2TitleTests.exe + true + + + + + true + + + + + true + + + + + 1 + .dylib + + + 0 + .bpl + + + Contents\MacOS + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + + + 1 + .dylib + + + 0 + .dll;.bpl + + + Contents\MacOS + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + + + 1 + + + 1 + + + 1 + + + + + Contents + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + res\drawable-normal + 1 + + + + + library\lib\x86 + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ../ + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + res\drawable-xlarge + 1 + + + + + res\drawable-xhdpi + 1 + + + + + 1 + + + 1 + + + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + library\lib\mips + 1 + + + + + res\drawable + 1 + + + + + Contents\MacOS + 1 + + + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + res\drawable-small + 1 + + + + + ../ + 1 + + + ../ + 1 + + + + + Contents\MacOS + 1 + + + 1 + + + Contents\MacOS + 0 + + + + + classes + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + res\drawable + 1 + + + + + Contents\Resources + 1 + + + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 0 + + + Contents\MacOS + 1 + + + 1 + + + 1 + + + + + library\lib\armeabi + 1 + + + + + res\drawable-large + 1 + + + + + 0 + + + 0 + + + 0 + + + Contents\MacOS + 0 + + + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\values + 1 + + + + + 1 + + + 1 + + + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + 1 + + + + + + + + + + + + False + True + False + + + DUnit / Delphi Win32 + GUI + C:\Users\Ilya\Documents\Dropbox\Documents\programming\Actual\URL2Title\URL2Title.dproj + + + 12 + + + + +
diff --git a/Test/URL2TitleTests.res b/Test/URL2TitleTests.res new file mode 100644 index 0000000000000000000000000000000000000000..743599575b02e97248bade49ed2e3eabafe25a0a GIT binary patch literal 96 zcmZQzU|>)H;{X347|28cOhBFu5dZ(r#Sp;Y!{Epe!r;c>&k)4m3uHM0X?F%!AS)QE O%YcEC1!e#vkO2UW7YiT& literal 0 HcmV?d00001 diff --git a/Test/Win32/Debug/dunit.ini b/Test/Win32/Debug/dunit.ini new file mode 100644 index 0000000..f464244 --- /dev/null +++ b/Test/Win32/Debug/dunit.ini @@ -0,0 +1,26 @@ +[GUITestRunner Config] +AutoSave=1 +Left=104 +Top=104 +Width=508 +Height=505 +Maximized=1 +UseRegistry=0 +ResultsPanel.Height=281 +ErrorMessage.Height=75 +ErrorMessage.Visible=1 +FailureList.ColumnWidth[0]=120 +FailureList.ColumnWidth[1]=100 +FailureList.ColumnWidth[2]=200 +FailureList.ColumnWidth[3]=1168 +HideTestNodesOnOpen=0 +BreakOnFailures=0 +FailOnNoChecksExecuted=0 +FailOnMemoryLeaked=0 +IgnoreSetUpTearDownLeaks=0 +ReportMemoryLeakTypes=0 +SelectTestedNode=1 +WarnOnFailTestOverride=0 +PopupX=350 +PopupY=30 + diff --git a/URL2Title.deployproj b/URL2Title.deployproj index afa8d72..84c3eb4 100644 --- a/URL2Title.deployproj +++ b/URL2Title.deployproj @@ -185,6 +185,15 @@ True True + + URL2Title\ + URL2Title.exe + 0 + + + True + True + diff --git a/URL2Title.dpr b/URL2Title.dpr index c7b80f8..927cc5d 100644 --- a/URL2Title.dpr +++ b/URL2Title.dpr @@ -2,14 +2,22 @@ program URL2Title; uses System.StartUpCopy, - FMX.Forms, FMX.Types, - uMain in 'uMain.pas' {FormMain}; + FMX.Forms, + FMX.Types, + uMain in 'uMain.pas' {FormMain} , + uWebScraper in 'uWebScraper.pas', + uUserAgent in 'uUserAgent.pas'; {$R *.res} begin +{$IFDEF DEBUG} + ReportMemoryLeaksOnShutdown := true; +{$ELSE} +{$ENDIF} FMX.Types.GlobalUseDirect2D := False; // avoid bug with AMD Dual Graphics Application.Initialize; Application.CreateForm(TFormMain, FormMain); Application.Run; + end. diff --git a/URL2Title.dproj b/URL2Title.dproj index 036b88b..f748f9e 100644 --- a/URL2Title.dproj +++ b/URL2Title.dproj @@ -7,7 +7,7 @@ True Release Win32 - 1119 + 31 Application @@ -18,16 +18,6 @@ Base true - - true - Base - true - - - true - Base - true - true Base @@ -139,96 +129,6 @@ FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;fmx;IndySystem;tethering;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapProviderClient;DbxCommonDriver;dbxcds;fmxFireDAC;CustomIPTransport;dsnap;IndyIPServer;IndyCore;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;DataSnapFireDAC;FireDACDBXDriver;soapserver;dsnapxml;bindcompfmx;RESTBackendComponents;emsclientfiredac;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;xmlrtl;DataSnapNativeClient;ibxpress;IndyProtocols;FireDACCommonDriver;bindengine;bindcompdbx;soaprtl;FMXTee;emsclient;FireDAC;inet;soapmidas;RESTComponents;dbexpress;IndyIPClient;$(DCC_UsePackage) package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=preferExternal;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey= - - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png - iPhoneAndiPad - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png - Base - Debug - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png - FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;fmx;IndySystem;tethering;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapProviderClient;DbxCommonDriver;dbxcds;fmxFireDAC;CustomIPTransport;dsnap;IndyIPServer;fmxase;IndyCore;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;DataSnapFireDAC;FireDACDBXDriver;soapserver;dsnapxml;bindcompfmx;RESTBackendComponents;emsclientfiredac;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;xmlrtl;DataSnapNativeClient;ibxpress;IndyProtocols;FireDACCommonDriver;bindengine;bindcompdbx;soaprtl;FMXTee;emsclient;FireDAC;inet;soapmidas;RESTComponents;dbexpress;IndyIPClient;$(DCC_UsePackage);$(DCC_UsePackage) - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png - true - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png - true - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png - true - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png - $(MSBuildProjectName) - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false - - - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png - iPhoneAndiPad - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png - Base - Debug - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png - FireDACSqliteDriver;FireDACDSDriver;DBXSqliteDriver;fmx;IndySystem;tethering;DBXInterBaseDriver;DataSnapClient;DataSnapCommon;DataSnapProviderClient;DbxCommonDriver;dbxcds;fmxFireDAC;CustomIPTransport;dsnap;IndyIPServer;fmxase;IndyCore;IndyIPCommon;CloudService;FmxTeeUI;FireDACIBDriver;DataSnapFireDAC;FireDACDBXDriver;soapserver;dsnapxml;bindcompfmx;RESTBackendComponents;emsclientfiredac;rtl;dbrtl;DbxClientDriver;FireDACCommon;bindcomp;xmlrtl;DataSnapNativeClient;ibxpress;IndyProtocols;FireDACCommonDriver;bindengine;bindcompdbx;soaprtl;FMXTee;emsclient;FireDAC;inet;soapmidas;RESTComponents;dbexpress;IndyIPClient;$(DCC_UsePackage);$(DCC_UsePackage) - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png - true - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png - true - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png - $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png - true - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png - $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png - $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png - $(MSBuildProjectName) - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png - CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false - $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png @@ -299,12 +199,12 @@ true - 1 - package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=preferExternal;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;FileVersion=1.0.0.1;LastCompiledTime=2015.04.04 15:42:35;apiKey= + 2 + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=preferExternal;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;FileVersion=1.0.0.2;LastCompiledTime=2015.04.09 03:31:20;apiKey= - 25 - CompanyName=;FileDescription=;FileVersion=1.0.0.25;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2015.04.04 15:04:34 + 30 + CompanyName=;FileDescription=;FileVersion=1.0.0.30;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2015.04.09 14:40:10 false @@ -335,33 +235,33 @@ false false false - 3 - package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=preferExternal;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;FileVersion=1.0.0.3;LastCompiledTime=2015.04.04 22:22:25;apiKey= + 4 + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=preferExternal;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;FileVersion=1.0.0.4;LastCompiledTime=2015.04.09 14:41:53;apiKey= - 7 + 8 URL2Title_Icns2.icns URL2Title_Icon2.ico /usr/X11/bin/xterm -e "%debuggee%" Debug true None - CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;FileVersion=1.0.0.7;LastCompiledTime=2015.04.04 22:10:34;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;FileVersion=1.0.0.8;LastCompiledTime=2015.04.09 14:44:40;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user URL2Title_Icon1.ico 1033 URL2Title_Icns1.icns true - 14 - CompanyName=;FileDescription=;FileVersion=1.0.0.14;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2015.04.04 21:49:28 + 24 + CompanyName=;FileDescription=;FileVersion=1.0.0.24;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2015.04.09 14:41:36 URL2Title_Icon.ico - 9 + 10 URL2Title_Icns.icns true - CompanyName=;FileDescription=;FileVersion=1.0.0.9;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2015.04.04 21:49:36 + CompanyName=;FileDescription=;FileVersion=1.0.0.10;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2015.04.09 14:41:43 1033 @@ -371,6 +271,8 @@
FormMain
+ + Cfg_2 Base @@ -521,6 +423,12 @@ true + + + URL2Title.exe + true + + true @@ -974,21 +882,23 @@ + - + True - True - True True True True True + + C:\Users\Ilya\Documents\Dropbox\Documents\programming\Actual\URL2Title\Test\URL2TitleTests.dproj + 12 diff --git a/URL2Title.res b/URL2Title.res index 6fdfa1e16452b404721a0ae2ef4039e1968b422f..36f26e234a8ac66a47e95cec6d496cf2f1442471 100644 GIT binary patch delta 6 NcmezJoLOOl0ssr`0d&) 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 diff --git a/URL2TitlePG.groupproj b/URL2TitlePG.groupproj new file mode 100644 index 0000000..2f5b372 --- /dev/null +++ b/URL2TitlePG.groupproj @@ -0,0 +1,48 @@ + + + {5C99E3DE-140E-4091-A7BA-9090C3B9F5E8} + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/uMain.pas b/uMain.pas index a47b9c9..1dfc99c 100644 --- a/uMain.pas +++ b/uMain.pas @@ -1,22 +1,21 @@ -unit uMain; +/// +/// The main window +/// +unit uMain; interface uses - System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Threading, + // System + System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + // FMX FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, FMX.Layouts, - FMX.Memo, Xml.xmldom, Xml.XMLIntf, Xml.XMLDoc, IdBaseComponent, IdComponent, IdTCPConnection, - IdTCPClient, IdHTTP, Generics.Collections, System.Generics.Defaults, System.RegularExpressions, - System.NetEncoding, System.Math, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack, IdSSL, - IdSSLOpenSSL, FMX.ScrollBox, FMX.Controls.Presentation {, UrlMon}; + FMX.Memo, FMX.ScrollBox, FMX.Controls.Presentation; type - TTitle = record - FIndex: Integer; - FURL: string; - FTitle: string; - end; - + /// + /// The main form + /// TFormMain = class(TForm) MemoURLs: TMemo; MemoResult: TMemo; @@ -25,8 +24,6 @@ TFormMain = class(TForm) PanelInput: TPanel; PanelOutput: TPanel; procedure ButtonGetTitlesClick(Sender: TObject); - procedure IdHTTP1Redirect(Sender: TObject; var dest: string; var NumRedirect: Integer; - var Handled: Boolean; var VMethod: string); private { Private declarations } public @@ -34,161 +31,27 @@ TFormMain = class(TForm) end; var + /// + /// The main form + /// FormMain: TFormMain; implementation {$R *.fmx} -procedure TFormMain.IdHTTP1Redirect(Sender: TObject; var dest: string; var NumRedirect: Integer; - var Handled: Boolean; var VMethod: string); -// handling HTTP(S) redirects (302 and so on) -begin - Handled := True; -end; - -(* - function SimpleIsValidURL(const URL: string): Boolean; - { - How about using the PathIsURL function in the Windows API? - Update: This is already wrapped in the Delphi RTL in the ShLwApi unit. - } - begin - if IsValidURL(nil, PWideChar(URL), 0) = S_OK then - Result := True - else - Result := False; - end; -*) - -function SimpleIsValidURL(const URL: string): Boolean; -const - URL_RegEx = '^http(s)?:\/\/.{3,}$'; -begin - Result := TRegEx.IsMatch(URL, URL_RegEx); -end; +uses uWebScraper; procedure TFormMain.ButtonGetTitlesClick(Sender: TObject); -var - ThreadListOfTitle: TThreadList; // Thread-safe generic list of TTitle - FinalListOfTitle: TList; // NOT thread-safe generic list of TTitle - Comparison: TComparison; // Custom generic comparator of TTitle - aTitle: TTitle; // Title (temp var) - CRLFconst: string; -const - timeout = 5000; // 5 seconds - useragent = 'Mozilla/5.0 (Windows NT 6.3; WOW64; rv:36.0) Gecko/20100101 Firefox/36.0'; // FF 36 begin MemoResult.Text := ''; if MemoURLs.Lines.Count > 0 then begin ButtonGetTitles.Text := 'Please wait...'; Application.ProcessMessages; - - ThreadListOfTitle := TThreadList.Create; try - ThreadListOfTitle.Duplicates := dupAccept; // for higer perfomance - // get all titles to list - // parallel for: 1 thread per processor core - TParallel.For(0, MemoURLs.Lines.Count - 1, - procedure(I: Integer) - var - IdHTTP1: TIdHTTP; // HTTP - IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL; // HTTPS - Title: TTitle; - PageContent: string; - SuccessFlag: Boolean; - procedure AddToThreadList(aTitle: TTitle); - // add item to thread-safe list - begin - ThreadListOfTitle.LockList; - try - ThreadListOfTitle.Add(aTitle); - SuccessFlag := True; - finally - ThreadListOfTitle.UnlockList; - end; - end; - - begin - if SimpleIsValidURL(MemoURLs.Lines[I]) then - begin - SuccessFlag := False; - try - IdHTTP1 := TIdHTTP.Create(Self); - try - IdHTTP1.ConnectTimeout := timeout; - IdHTTP1.ReadTimeout := timeout; - IdHTTP1.Request.useragent := useragent; // TODO: add other request params - // ... - IdHTTP1.AllowCookies := True; - IdHTTP1.HandleRedirects := True; - IdHTTP1.RedirectMaximum := 10; - IdHTTP1.OnRedirect := IdHTTP1Redirect; - IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(Self); - try - IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL1; - PageContent := IdHTTP1.Get(IdHTTP1.URL.URLEncode(MemoURLs.Lines[I])); - // Auto charset - Title.FIndex := I; - Title.FURL := MemoURLs.Lines[I]; - // search for title tag - Title.FTitle := TRegEx.Match(PageContent, ']*>(.*?)', - [roIgnoreCase, roMultiLine]).Value; - // remove html tags and comments - Title.FTitle := TRegEx.Replace(Title.FTitle, - ']*>|', '', [roIgnoreCase, roMultiLine]); - // decode html entities - Title.FTitle := TNetEncoding.HTML.Decode(Title.FTitle); - AddToThreadList(Title); - finally - FreeAndNil(IdSSLIOHandlerSocketOpenSSL1); - end; - finally - IdHTTP1.Disconnect; - FreeAndNil(IdHTTP1); - end; - except - on E: Exception do - if not SuccessFlag then - begin - CRLFconst := sLineBreak; - // - Title.FIndex := I; - Title.FURL := MemoURLs.Lines[I]; - Title.FTitle := TRegEx.Replace(E.ClassName + ': ' + E.Message, sLineBreak, ' '); - // for Windows - Title.FTitle := TRegEx.Replace(E.ClassName + ': ' + E.Message, - CRLFconst.Substring(CRLFconst.Length - 1), ' '); - // for OS X and Android - AddToThreadList(Title); - end; - end; - end - else - begin - Title.FIndex := I; - Title.FURL := MemoURLs.Lines[I]; - Title.FTitle := 'Is not valid URL'; - AddToThreadList(Title); - end; - end); - // show list in memo - FinalListOfTitle := ThreadListOfTitle.LockList; - try - Comparison := function(const Left, Right: TTitle): Integer - begin - // Result := CompareValue(Item1.FIndex, Item2.FIndex); // but... I am a Hipster! - Result := TComparer.Default.Compare(Left.FIndex, Right.FIndex); - end; - FinalListOfTitle.Sort(TComparer.Construct(Comparison)); - for aTitle in FinalListOfTitle do - MemoResult.Lines.Add(aTitle.FTitle); - finally - ThreadListOfTitle.UnlockList; - end; + TWebScraper.GetTitlesFromURLs(MemoURLs.Lines, MemoResult.Lines); finally - FreeAndNil(ThreadListOfTitle); ButtonGetTitles.Text := 'Get titles'; end; end; diff --git a/uUserAgent.pas b/uUserAgent.pas new file mode 100644 index 0000000..82109ec --- /dev/null +++ b/uUserAgent.pas @@ -0,0 +1,129 @@ +/// +/// This unit can be used for getting real UserAgent from UA list +/// +unit uUserAgent; + +interface + +type + /// + /// This class can be used for getting real UserAgent from UA list + /// + TUserAgent = class + private + /// + /// Get random UserAgent of any browser + /// + /// + /// Index of array in CFullUAList + /// + /// + /// ABrowserIndex = (CFirefoxIndex, CChromeIndex) + /// + class function GetRandomAnyBrowserUA(ABrowserIndex: integer): string; + public + /// + /// Get random UserAgent + /// + class function GetRandomUA: string; + /// + /// Get random UserAgent of Firefox browser + /// + class function GetRandomFirefoxUA: string; + /// + /// Get random UserAgent of Chrome browser + /// + class function GetRandomChromeUA: string; + end; + +const + /// + /// Index of Firefox UA list in CFullUAList + /// + CFirefoxIndex = 0; + /// + /// Index of Chrome UA list in CFullUAList + /// + CChromeIndex = 1; + + /// + /// List of real UserAgent strings + /// + CFullUAList: TArray < TArray < string >> = [ + // from useragentstring.com + // Firefox UA + ['Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10; rv:33.0) Gecko/20100101 Firefox/33.0', + 'Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0', + 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0', + 'Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0', + 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0', + 'Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0', + 'Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0', + 'Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.3', + 'Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.0', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0', + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0', + 'Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0', + 'Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/23.0', + 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.0', + 'Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/22.0', + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.0', + 'Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.0'], + + // Chrome UA + ['Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36', + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2224.3 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.36', + 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36', + 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36', + 'Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36', + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1944.0 Safari/537.36'] + ]; + +implementation + +uses System.StrUtils; + +{ TUserAgentManager } + +class function TUserAgent.GetRandomAnyBrowserUA(ABrowserIndex: integer): string; +begin + Result := RandomFrom(CFullUAList[ABrowserIndex]); +end; + +class function TUserAgent.GetRandomChromeUA: string; +begin + GetRandomAnyBrowserUA(CChromeIndex); +end; + +class function TUserAgent.GetRandomFirefoxUA: string; +begin + GetRandomAnyBrowserUA(CFirefoxIndex); +end; + +class function TUserAgent.GetRandomUA: string; +var + ABrowserIndex: integer; +begin + ABrowserIndex := Random(Length(CFullUAList)); + Result := GetRandomAnyBrowserUA(ABrowserIndex); +end; + +initialization + +Randomize; + +end. diff --git a/uWebScraper.pas b/uWebScraper.pas new file mode 100644 index 0000000..fea195b --- /dev/null +++ b/uWebScraper.pas @@ -0,0 +1,230 @@ +/// +/// This unit can be used for getting the list of page titles by the list of +/// URLs +/// +/// +/// Indy HTTP component +/// +unit uWebScraper; + +interface + +uses + // Indy + IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP, IdIOHandler, + IdIOHandlerSocket, IdIOHandlerStack, IdSSL, IdSSLOpenSSL, IdURI, IdGlobal, + // System + Generics.Collections, System.Generics.Defaults, System.RegularExpressions, System.NetEncoding, + System.Math, System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, + System.Threading; + +type + /// + /// Represents a single web page element. + /// + /// + /// If exception will be raised, it will be stored in FTitle field + /// + /// + /// FIndex = 0; FURL = 'https://www.google.com'; FTitle = 'Google'; + /// + TTitle = record + /// + /// Index of current page URL in list + /// + FIndex: Integer; + /// + /// Page URL + /// + FURL: string; + /// + /// Page title + /// + FTitle: string; + end; + + /// + /// This class can get a list of page titles from list of URLs + /// + TWebScraper = class + private + /// + /// Check if text is a valid URL. + /// + /// + /// Page URL + /// + class function IsValidURL_RegEx(const URL: string): Boolean; + /// + /// Milliseconds to wait for successful completion of a connection + /// attempt / a read operation. + /// + class procedure IdHTTP1Redirect(Sender: TObject; var dest: string; var NumRedirect: Integer; + var Handled: Boolean; var VMethod: string); + /// + /// Get page title using IdHTTP component. + /// + class function GetTitleByIdHTTP(const AURL: string; const I: Integer): TTitle; + const + /// + /// ConnectTimeout is a public Integer property that indicates the + /// maximum number of milliseconds to wait for successful completion of a + /// connection attempt on the client. + /// + CIdTimeout = 5000; // 5 seconds + /// + /// Maximum allowed number of redirects. + /// + CIdRedirectMaximum = 10; + + public + /// + /// Get page titles from list of URLs. + /// + class procedure GetTitlesFromURLs(URLStrings, ResultStrings: TStrings); + end; + +implementation + +uses uUserAgent; + +class procedure TWebScraper.IdHTTP1Redirect(Sender: TObject; var dest: string; + var NumRedirect: Integer; var Handled: Boolean; var VMethod: string); +// handling HTTP(S) redirects (302 and so on) +begin + Handled := True; +end; + +class function TWebScraper.IsValidURL_RegEx(const URL: string): Boolean; +const + URL_RegEx = '^http(s)?:\/\/.{3,}$'; +begin + Result := TRegEx.IsMatch(URL, URL_RegEx); +end; + +class function TWebScraper.GetTitleByIdHTTP(const AURL: string; const I: Integer): TTitle; +var + IdHTTP1: TIdHTTP; // HTTP + IdSSLIOHandlerSocketOpenSSL1: TIdSSLIOHandlerSocketOpenSSL; // HTTPS + PageContent: string; +begin + Result.FIndex := -1; + Result.FURL := 'Error'; + Result.FTitle := 'Error'; + + IdHTTP1 := TIdHTTP.Create(nil); + try + IdHTTP1.ConnectTimeout := CIdTimeout; + IdHTTP1.ReadTimeout := CIdTimeout; + IdHTTP1.Request.UserAgent := TUserAgent.GetRandomUA; + IdHTTP1.AllowCookies := True; + IdHTTP1.HandleRedirects := True; + IdHTTP1.RedirectMaximum := CIdRedirectMaximum; + IdHTTP1.OnRedirect := TWebScraper.IdHTTP1Redirect; + IdSSLIOHandlerSocketOpenSSL1 := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + IdHTTP1.IOHandler := IdSSLIOHandlerSocketOpenSSL1; + PageContent := IdHTTP1.Get(IdHTTP1.URL.URLEncode(AURL)); + // Auto charset + Result.FIndex := I; + Result.FURL := AURL; + // search for title tag + Result.FTitle := TRegEx.Match(PageContent, ']*>(.*?)', + [roIgnoreCase, roMultiLine]).Value; + // remove html tags and comments + Result.FTitle := TRegEx.Replace(Result.FTitle, ']*>|', '', + [roIgnoreCase, roMultiLine]); + // decode html entities + Result.FTitle := TNetEncoding.HTML.Decode(Result.FTitle); + finally + FreeAndNil(IdSSLIOHandlerSocketOpenSSL1); + end; + finally + IdHTTP1.Disconnect; + FreeAndNil(IdHTTP1); + end; +end; + +class procedure TWebScraper.GetTitlesFromURLs(URLStrings, ResultStrings: TStrings); +var + /// Thread-safe generic list of TTitle + ThreadListOfTitle: TThreadList; + /// NOT thread-safe generic list of TTitle + FinalListOfTitle: TList; + /// Declares a reference to a generic callback function used to compare + /// two TTitle records. + Comparison: TComparison; + /// Title (temp var) + TMPTitle: TTitle; +begin + ThreadListOfTitle := TThreadList.Create; + try + ThreadListOfTitle.Duplicates := dupAccept; // for higer perfomance + // get all titles to list + // parallel for: 1 thread per processor core + TParallel.For(0, URLStrings.Count - 1, + + procedure(I: Integer) + var + Title: TTitle; + SuccessFlag: Boolean; + + procedure AddToThreadList(ATitle: TTitle); + // add item to thread-safe list + begin + ThreadListOfTitle.LockList; + try + ThreadListOfTitle.Add(ATitle); + SuccessFlag := True; + finally + ThreadListOfTitle.UnlockList; + end; + end; + + begin + if IsValidURL_RegEx(URLStrings[I]) then + begin + SuccessFlag := False; + try + Title := GetTitleByIdHTTP(URLStrings[I], I); + AddToThreadList(Title); + except + on E: Exception do + if not SuccessFlag then + begin + Title.FIndex := I; + Title.FURL := URLStrings[I]; + Title.FTitle := E.ClassName + ': ' + E.Message; + // remove line breaks (CR+LF or LF) + Title.FTitle := Title.FTitle.Replace(ResultStrings.LineBreak, ' '); + AddToThreadList(Title); + end; + end; + end + else + begin + Title.FIndex := I; + Title.FURL := URLStrings[I]; + Title.FTitle := 'Is not valid URL'; + AddToThreadList(Title); + end; + end); + // show list in memo + FinalListOfTitle := ThreadListOfTitle.LockList; + try + Comparison := function(const Left, Right: TTitle): Integer + begin + Result := TComparer.Default.Compare(Left.FIndex, Right.FIndex); + end; + FinalListOfTitle.Sort(TComparer.Construct(Comparison)); + for TMPTitle in FinalListOfTitle do + ResultStrings.Add(TMPTitle.FTitle); + finally + ThreadListOfTitle.UnlockList; + end; + finally + FreeAndNil(ThreadListOfTitle); + end; +end; + +end.