I asked the same question Web scraper for e-commerce sites yesterday and I'm now posting the revised code here.
I'm building web scraper application which takes name, code and price from few sites. I thought factory pattern would fit in my application. I would like to someone review my code and tell if I'm missing something.
I have class Item
which holds scraped data.
public class Item
{
public string Code { get; set; }
public string Name { get; set; }
public string Price { get; set; }
}
An interfacem which has a method RunScrapingAsync
with a list of item codes as the single parameter, which I need to scrape.
public interface IWebScraper
{
Task<List<Item>> RunScrapingAsync(List<string> itemCodes);
}
Then I have implementations for three scrapers (Amazon, EBay, AliExpress):
public class AmazonWebScraper : IWebScraper
{
private static HttpClient client;
public List<string> ItemCodes { get; set; }
public AmazonWebScraper()
{
client = new HttpClient(new HttpClientHandler() { Proxy = null });
client.BaseAddress = new Uri("https://amazon.com");
}
public async Task<List<Item>> RunScrapingAsync(List<string> itemCodes)
{
ConcurrentBag<Item> itemsConcurrentBag = new ConcurrentBag<Item>();
//for simplicity this logic is not important no need to go in details
return itemsConcurrentBag.ToList();
}
}
public class EBayWebScraper : IWebScraper
{
private static HttpClient client;
public List<string> ItemCodes { get; set; }
public EBayWebScraper()
{
client = new HttpClient(new HttpClientHandler() { Proxy = null });
client.BaseAddress = new Uri("https://ebay.com");
}
public async Task<List<Item>> RunScrapingAsync(List<string> itemCodes)
{
ConcurrentBag<Item> itemsConcurrentBag = new ConcurrentBag<Item>();
//for simplicity this logic is not important no need to go in details
return itemsConcurrentBag.ToList();
}
}
public class AliExpressWebScraper : IWebScraper
{
private static HttpClient client;
public List<string> ItemCodes { get; set; }
public AliExpressWebScraper()
{
client = new HttpClient(new HttpClientHandler() { Proxy = null });
client.BaseAddress = new Uri("https://aliexpress.com");
}
public async Task<List<Item>> RunScrapingAsync(List<string> itemCodes)
{
ConcurrentBag<Item> itemsConcurrentBag = new ConcurrentBag<Item>();
//for simplicity this logic is not important no need to go in details
return itemsConcurrentBag.ToList();
}
}
Here is my factory class WebScraperFactory
:
public enum WebSite
{
Amazon,
EBay,
AliExpress
}
public class WebScraperFactory
{
public IWebScraper Create(WebSite website)
{
switch (website)
{
case WebSite.Amazon:
return new AmazonWebScraper();
case WebSite.EBay:
return new EBayWebScraper();
case WebSite.AliExpress:
return new AliExpressWebScraper();
default:
throw new NotImplementedException($"Not implemented create method in scraper factory for website {webSite}");
}
}
}
The WebScraper
class, which holds all scrapers in a dictionary and uses them in the Execute
method for the provided WebSite
.
public class WebScraper
{
private readonly WebScraperFactory _webScraperFactory;
public WebScraper()
{
_webScraperFactory = new WebScraperFactory();
}
public async Task<List<Item>> Execute(WebSite webSite, List<string> itemCodes) =>
await _webScraperFactory.Create(webSite).RunScrapingAsync(itemCodes);
}
This is a WinForm app, so users have the option to run one or more scrapers (they are not all mandatory to run). So if a user chooses to run Amazon and AliExpress, it will choose two files with codes, adds them to the dictionary and calls the webscraper factory on every chosen website.
Example usage:
var codes = new Dictionary<WebSite, List<string>>
{
{WebSite.Amazon, amazonCodes},
{WebSite.AliExpress, aliExpressCodes}
}
var items = new Dictionary<WebSite, List<Item>>
{
{WebSite.Amazon, null},
{WebSite.AliExpress, null}
}
var webScraper = new WebScraper();
foreach(var webSite in websitesItemCodes.Keys)
{
items[webSite] = await webScraper.Execute(webSite, codes[webSite]);
}
static
HttpClient
but assigning it in a non-static constructor is, for all intents and purposes, wrong. Either remove thestatic
, or inject the entireHttpClient
into your object and have it truly static elsewhere. \$\endgroup\$