diff --git a/MMOGAScraper/Product.cs b/MMOGAScraper/Product.cs index 4b08c4a..3db771a 100644 --- a/MMOGAScraper/Product.cs +++ b/MMOGAScraper/Product.cs @@ -2,7 +2,7 @@ { public class Product { - public Product(bool isNotAvailable, string shopCategory, string cover, ProductType type, string link, string title, decimal price, bool isPriceReduced, decimal reducedPrice, string platformLogo, ProductDeliveryTime deliveryTime, DateTime deliveryTimeCustomDate, ProductAvailability availability, string region, string platform, string description, string extendedDescription, bool isPaypalAvailable) + public Product(bool isNotAvailable, string shopCategory, string cover, ProductType type, string link, string title, decimal price, bool isPriceReduced, decimal reducedPrice, string platformLogo, ProductDeliveryTime deliveryTime, DateTime deliveryTimeCustomDate, ProductAvailability availability, string region, string platform, string description, string extendedDescription, Requirements systemRequirements, bool isPaypalAvailable) { IsNotAvailable = isNotAvailable; ShopCategory = shopCategory; @@ -21,6 +21,7 @@ public Product(bool isNotAvailable, string shopCategory, string cover, ProductTy Platform = platform; Description = description; ExtendedDescription = extendedDescription; + SystemRequirements = systemRequirements; IsPaypalAvailable = isPaypalAvailable; } @@ -41,6 +42,7 @@ public Product(bool isNotAvailable, string shopCategory, string cover, ProductTy public string Platform { get; set; } public string Description { get; set; } public string ExtendedDescription { get; set; } + public Requirements SystemRequirements { get; set; } public bool IsPaypalAvailable { get; set; } } } \ No newline at end of file diff --git a/MMOGAScraper/Program.cs b/MMOGAScraper/Program.cs index 75193f6..6d6ef04 100644 --- a/MMOGAScraper/Program.cs +++ b/MMOGAScraper/Program.cs @@ -7,7 +7,7 @@ public static void Main() MmogaScraper scraper = new(ScraperRegion.DE); const string querystring = "fifa"; //What to search for on mmoga.de - const int pages = 8; + const int pages = 2;//What number of pages to search for on mmoga.de Console.WriteLine($"Querying: {querystring} for {pages} pages"); #region Page number example @@ -90,6 +90,18 @@ private static void PrintProduct(Product product) { Console.WriteLine($"Delivery time: {product.DeliveryTime}"); } + if (product.SystemRequirements.Available) + { + if (product.SystemRequirements.Uncomplete) + { + Console.WriteLine($"Requirements: {product.SystemRequirements.Text}"); + } + else + { + Console.WriteLine($"Requirements (minimum): {product.SystemRequirements.Minimum}"); + Console.WriteLine($"Requirements (maximum): {product.SystemRequirements.Maximum}"); + } + } Console.WriteLine($"Paypal available: {product.IsPaypalAvailable}"); Console.WriteLine($"Region: {product.Region}"); Console.WriteLine($"Platform: {product.Platform}"); diff --git a/MMOGAScraper/Requirements.cs b/MMOGAScraper/Requirements.cs new file mode 100644 index 0000000..ea6810b --- /dev/null +++ b/MMOGAScraper/Requirements.cs @@ -0,0 +1,25 @@ +namespace MMOGAScraper +{ + public class Requirements + { + public Requirements(bool available, string minimum, string maximum, bool uncomplete, string text) + { + Available = available; + Minimum = minimum; + Maximum = maximum; + Uncomplete = uncomplete; + Text = text; + } + + public bool Available { get; set; } + public string Minimum { get; set; } + public string Maximum { get; set; } + public bool Uncomplete { get; set; } + public string Text { get; set; } + + public override string ToString() + { + return $"{{{nameof(Available)}={Available}, {nameof(Minimum)}={Minimum}, {nameof(Maximum)}={Maximum}, {nameof(Uncomplete)}={Uncomplete}, {nameof(Text)}={Text}}}"; + } + } +} \ No newline at end of file diff --git a/MMOGAScraper/ScraperMethods.cs b/MMOGAScraper/ScraperMethods.cs index f0fbcee..7e99220 100644 --- a/MMOGAScraper/ScraperMethods.cs +++ b/MMOGAScraper/ScraperMethods.cs @@ -82,9 +82,11 @@ internal static Product GetProductType(string ProductLink) IsConsoleGame = true; } + Requirements requirements = new(false, "", "", true, ""); ProductType Type; if (doc.DocumentNode.SelectSingleNode("/html/body/div[2]/div/div[5]/ul/li[2]/p") != null && !IsDlc && !IsGiftcard && !IsRandomObject) { + requirements = ParseRequirementsNode(doc, requirements); Type = ProductType.Game; } else if (IsDlc) @@ -223,7 +225,7 @@ internal static Product GetProductType(string ProductLink) #region Product object - Product product = new(IsGameNotAvailable, ShopCategory, CoverImage, Type, ProductLink, ProductTitle, ReturnDecimalValue(Price), PriceReduced, ReturnDecimalValue(ReducedPrice), PlatformLogoSource, DeliveryTime, CustomDeliveryTime, Availability, Region, Platform, ProductDescription, DescriptionText, IsPaypalAvailable); + Product product = new(IsGameNotAvailable, ShopCategory, CoverImage, Type, ProductLink, ProductTitle, ReturnDecimalValue(Price), PriceReduced, ReturnDecimalValue(ReducedPrice), PlatformLogoSource, DeliveryTime, CustomDeliveryTime, Availability, Region, Platform, ProductDescription, DescriptionText, requirements, IsPaypalAvailable); #endregion Product object @@ -233,6 +235,37 @@ internal static Product GetProductType(string ProductLink) return null; } + private static Requirements ParseRequirementsNode(HtmlDocument doc, Requirements requirements) + { + HtmlNode RequirementsData = doc.DocumentNode.SelectSingleNode("/html/body/div[2]/div/div[5]/ul/li[2]/div/p[2]"); + string RequirementsText = WhitespaceNewLineRemover(RequirementsData.InnerText); + if (RequirementsText.Contains("Minimum") && RequirementsText.Contains("Empfohlen")) + { + string[] RequirementsTextParsed = RequirementsText.Split("Empfohlen"); + string RequirementsMinimum = RequirementsTextParsed[0].Split("Minimum")[1]; + string RequirementsMaximum = RequirementsTextParsed[1]; + RequirementsMinimum = FilterRequirementsData(RequirementsMinimum, false); + RequirementsMaximum = FilterRequirementsData(RequirementsMaximum, false); + requirements = new(true, RequirementsMinimum, RequirementsMaximum, false, ""); + } + else + { + requirements.Text = FilterRequirementsData(RequirementsText, true); + } + + return requirements; + } + + private static string FilterRequirementsData(string input, bool small) + { + if (small) + { + input = input.Replace("Betriebssystem:", " Betriebssystem:"); + } + input = input.Replace("Prozessor:", " Prozessor:").Replace("Arbeitsspeicher:", " Arbeitsspeicher:").Replace("Grafikkarte:", " Grafikkarte:").Replace("Speicherplatz:", " Speicherplatz:").Replace("Breitband", " Breitband"); + return input; + } + internal static int CalculateQueryPageNumber(HtmlDocument doc) { int MaxPagesForProduct = 1; diff --git a/README.md b/README.md index a05af57..ec9e6ea 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,52 @@ -# MMOGA Product Scraper - MMOGA-PS (API) +# MMOGA Product Scraper (API) -#### Projekt von https://github.com/liebki +##### Projekt von https://github.com/liebki -A scraper to get products including the (possible) data of https://mmoga.de +## Introduction + +### Why does this exist? + +I originally needed a way to parse prices for games, for [BlazorLibrary](https://github.com/liebki/BlazorLibrary), now I created this and I might implement it in some time. + +### General + +So this tool/api, is scraper to get products (and their data) of https://mmoga.de (And soon all other regions like US, UK etc.) ## Technologies ### Created using - .NET Core 6.0 -### Nuget(s) -- HtmlAgilityPack +### Nugets/Dependencies +- [HtmlAgilityPack](https://www.nuget.org/packages/HtmlAgilityPack/) ## Features ### What data is available right now? -- To see what data is available, please take a look in the Product.cs and/or LightProduct.cs + +To see what data is available, please take a look in the [LightProduct](https://github.com/liebki/MMOGA-Product-Scraper/blob/master/MMOGAScraper/LightProduct.cs)/[Product](https://github.com/liebki/MMOGA-Product-Scraper/blob/master/MMOGAScraper/Product.cs).cs classes ### General -- Get a list of product objects, those can be used to get prices, availabilities, platforms etc. -- Some products wont be found using the DeeperSearch, because atm I didn't include a way to parse them -- To get **every** (possible made) product, use the QuickSearch but you will loose detail -- I will include every product in the DeeperSearch but it is one of my things on the TO-DO so you have to wait + +Get a ```List``` or ```List```, the objects inside of the lists (depending on the object), contain data like availabilities, prices and much more. + +#### Information + +Some products wont be parsed by the "DeeperSearch", to get **every** (parseable) product, use the "QuickSearch" (you will loose some data, compare the LightProduct and Product to know which). +Im working on including every product for the "DeeperSearch" but it is one of my things on the TO-DO so you have to wait ## Usage ### Code/Methods to use -The MmogaScraper class contains a "QuickSearch" and a "DeeperSearch" method. -QuickSearch will provide you faster results but with fewer detail, it will miss following data of the DeeperSearch: - Type - Platform/Logo - Delivery time - Region - Description - Extended description - Paypal availability - -Because of this there is a LightProduct (QuickSearch) and a Product (DeeperSearch). +The MmogaScraper class contains a "QuickSearch", "DeeperSearch", "PagenumberSearch" and "DeeperSearch_Alpha" method. +- QuickSearch: Provides you with faster results but with fewer detail +- DeeperSearch: Provides you with the complete data but it is slower +- PagenumberSearch: Provides you with the number of pages a query gives back +- DeeperSearch_Alpha: Provides you with the complete data but it is slower and you can select the number of pages you want to have the data of ``` + //Get the number of pages a query produces int QueryPagenumber = MmogaScraper.PagenumberSearch("what I got"); @@ -52,22 +59,26 @@ List ProductList = MmogaScraper.DeeperSearch("what you need"); //This version of DeeperSearch is not 100% finished, tho it is working! //Get all the data, using the slower search AND search on more than just the first page List ProductListAlpha = MmogaScraper.DeeperSearch_Alpha("search them good", pages_as_int); + ``` ## Example -### Code (Program.cs) -Please take a look in the *Program.cs*, the code there is showing a working example for scraping "fifa", using Quick- and DeeperSearch. +### Code + +Please take a look in the *[Program.cs](https://github.com/liebki/MMOGA-Product-Scraper/blob/master/MMOGAScraper/Program.cs)*, the code there is showing a working example for scraping "fifa", using +"QuickSearch", "DeeperSearch_Alpha" and "PagenumberSearch". ### Output + GIF shows content of "cyberpunk" scraping: ![Logo](https://iili.io/ksX3Vp.gif) ## FAQ -#### Does this work on every mmoga page? +#### Does this work on every mmoga region? -I created this for mmoga.**DE** I don't know about mmoga.com or other regions in general +I created this explicitly for mmoga.(DE) I don't know about mmoga.com or other regions in general, but I am working on including them. #### Why can't I obtain data X? @@ -75,7 +86,7 @@ I'm trying my best, so please be patient or include the things you like to see y #### Is it working? -Right now in end of July '22, this tool is working pretty good +Right now in end of July '22, this tool is working pretty nice! ## License @@ -90,8 +101,10 @@ Right now in end of July '22, this tool is working pretty good ## Roadmap #### Sorted by importancy -- Include more pages than just the first page of the search (WIP, 50% done) -- Option to change region of mmoga +- Include more pages than just the first page of the search (Almost done, WIP) +- Option to change region of mmoga (WIP) +- Create more objects for things, like payment ways etc. +- Parse similar products on a products site - Parse "coming soon" products - Parse "hits" products - Parse "preorder" products