MangaAndLightNovelWebScrape 6.0.0

dotnet add package MangaAndLightNovelWebScrape --version 6.0.0
                    
NuGet\Install-Package MangaAndLightNovelWebScrape -Version 6.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="MangaAndLightNovelWebScrape" Version="6.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="MangaAndLightNovelWebScrape" Version="6.0.0" />
                    
Directory.Packages.props
<PackageReference Include="MangaAndLightNovelWebScrape" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add MangaAndLightNovelWebScrape --version 6.0.0
                    
#r "nuget: MangaAndLightNovelWebScrape, 6.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package MangaAndLightNovelWebScrape@6.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=MangaAndLightNovelWebScrape&version=6.0.0
                    
Install as a Cake Addin
#tool nuget:?package=MangaAndLightNovelWebScrape&version=6.0.0
                    
Install as a Cake Tool

MangaAndLightNovelWebScrape

(Manga & Light Novel Web Scrape Framework for .NET) - ChangeLog

.NET 10 library that scrapes manga and light-novel retailers, compares the results across every site you ask for, and hands back the cheapest copy of each volume. Stock-aware dedup keeps purchasable copies over cheaper-but-out-of-stock reprints, per-site failures stay isolated from siblings, every scrape supports cooperative cancellation, and a reachability probe lets you short-circuit known-down sites before launching. Provider-agnostic logging through Microsoft.Extensions.Logging.


Installation

dotnet add package MangaAndLightNovelWebScrape

Targets net10.0. Sites that require JavaScript rendering drive Playwright under the hood — currently AmazonUSA, AmazonJP, BooksAMillion, ForbiddenPlanet, KinokuniyaUSA, MangaMart, MangaMate, MerryManga. The runtime downloads browsers on first use; you can also pre-install them via the CLI:

pwsh bin/Debug/net10.0/playwright.ps1 install msedge

HTML-only sites — AllStarComics, Crunchyroll, InStockTrades, KingsComics, OKComics, RobertsAnimeCornerStore, SciFier, TravellingMan — skip Playwright entirely, so a scrape that only includes them never launches a browser.


Quick Start

using MangaAndLightNovelWebScrape;
using MangaAndLightNovelWebScrape.Enums;

MasterScrape scrape = new(StockStatusFilter.EXCLUDE_OOS_AND_PO_FILTER)
{
    Region = Region.America,
};

await scrape.InitializeScrapeAsync(
    title: "jujutsu kaisen",
    bookType: BookType.Manga,
    siteList: [Website.InStockTrades, Website.RobertsAnimeCornerStore]);

scrape.PrintResultsToConsole(isAsciiTable: true,
    title: "jujutsu kaisen", bookType: BookType.Manga);

The same MasterScrape instance can be reused for any number of subsequent calls — see Creating Master Scrape below.


Website Completion List

Status legend: ✅ Working · ⌛ Planned / Paused · ❌ Retired or non-working. Render column tells you whether a scrape that only includes that site needs Playwright. Manga / Light Novel columns reflect what the site itself stocks — sites that don't stock LightNovel silently return zero entries for BookType.LightNovel (see Manga-only sites).

If you want a website or region to be added, file an issue request.

America
Site Status Render Manga Light Novel Notes
AmazonUSA ✅ Playwright ✅ ✅
BooksAMillion ✅ Playwright ✅ ✅ Membership: Membership.BooksAMillion
Crunchyroll ✅ HTML ✅ ✅
InStockTrades ✅ HTML ✅ ✅
KinokuniyaUSA ✅ Playwright ✅ ✅ Membership: Membership.KinokuniyaUSA. Manga entries occasionally drop when the on-page Manga facet excludes them.
MangaMart ✅ Playwright ✅ ✅
MerryManga ✅ Playwright ✅ ✅
RobertsAnimeCornerStore ✅ HTML ✅ ✅
SciFier ✅ HTML ✅ ✅ Multi-region — same site backs Australia, Britain, Canada, Europe too.
Barnes & Noble ❌ — — — Retired — robots.txt change.
Australia
Site Status Render Manga Light Novel Notes
AllStarComics ✅ HTML ✅ ❌ Manga-only — silently skips LightNovel scrapes.
KingsComics ✅ HTML ✅ ❌ Manga-only — silently skips LightNovel scrapes.
MangaMate ✅ Playwright ✅ ✅
SciFier ✅ HTML ✅ ✅
AmazonAU ⌛ — — — Not started.
Britain
Site Status Render Manga Light Novel Notes
ForbiddenPlanet ✅ Playwright ✅ ✅
OKComics ✅ HTML ✅ ❌ Manga-only — silently skips LightNovel scrapes.
SciFier ✅ HTML ✅ ✅
TravellingMan ✅ HTML ✅ ✅
AmazonUK ⌛ — — — Not started.
SpeedyHen ❌ — — — Retired — Cloudflare CAPTCHA.
Waterstones ❌ — — — Retired — Cloudflare CAPTCHA.
Canada
Site Status Render Manga Light Novel Notes
SciFier ✅ HTML ✅ ✅
AmazonCanada ⌛ — — — Not started.
Indigo ❌ — — — Not working — pending re-audit.
Europe
Site Status Render Manga Light Novel Notes
SciFier ✅ HTML ✅ ✅
Japan
Site Status Render Manga Light Novel Notes
AmazonJP ⌛ — — — Not started.
CDJapan ⌛ — — — Paused — partial implementation, not registered.

Creating Master Scrape

MasterScrape is designed to be constructed once and reused across many scrape runs — InitializeScrapeAsync clears all per-run state at the top of the call (results, URL map, error map). Allocating a fresh instance per call just wastes the constructor's work.

Defaults: Region.America, Browser.Edge, Membership.None, debug mode off.

using MangaAndLightNovelWebScrape;
using MangaAndLightNovelWebScrape.Enums;

// Minimal — only the stock-status filter is required.
MasterScrape scrape = new(StockStatusFilter.EXCLUDE_NONE_FILTER);

// You can also mutate the properties after construction.
scrape.Region = Region.Canada;
scrape.Browser = Browser.FireFox;
scrape.Filter = StockStatusFilter.EXCLUDE_OOS_AND_PO_FILTER;
// Memberships is a [Flags] enum — combine sites with `|`.
scrape.Memberships = Membership.BooksAMillion | Membership.KinokuniyaUSA;

Alternatively, set everything in the constructor. Multi-region scrapes (e.g. Region.America | Region.Britain) are not supported — one region per scrape.

MasterScrape scrape = new(
    Filter: StockStatusFilter.EXCLUDE_NONE_FILTER,
    Region: Region.Britain,
    Browser: Browser.Edge,
    Memberships: Membership.BooksAMillion | Membership.KinokuniyaUSA);
Logging

The library writes through Microsoft.Extensions.Logging.Abstractions — pass an ILoggerFactory (Serilog, NLog, the console provider, your own) to the constructor and the scrape will emit per-site events to it. When no factory is supplied, logging is a no-op.

using Microsoft.Extensions.Logging;

using ILoggerFactory loggerFactory = LoggerFactory.Create(b => b
    .SetMinimumLevel(LogLevel.Information)
    .AddSimpleConsole());

MasterScrape scrape = new(
    Filter: StockStatusFilter.EXCLUDE_NONE_FILTER,
    Region: Region.America,
    loggerFactory: loggerFactory);

Calling .EnableDebugMode() additionally writes per-site result dumps to <cwd>/Data/<Site>Data.txt — useful for local diagnosis.


Initializing a Scrape

Pass a HashSet<Website> of sites to scrape. The optional CancellationToken lets you cancel between major phases (site fan-out, merge rounds).

await scrape.InitializeScrapeAsync(
    title: "one piece",
    bookType: BookType.Manga,
    siteList: [Website.RobertsAnimeCornerStore, Website.Crunchyroll]);
// With cancellation
using CancellationTokenSource cts = new(TimeSpan.FromMinutes(2));
await scrape.InitializeScrapeAsync(
    title: "overlord",
    bookType: BookType.LightNovel,
    siteList: [Website.SciFier, Website.BooksAMillion],
    cancellationToken: cts.Token);
Manga-only sites

Some retailers don't stock prose light novels — AllStarComics, KingsComics, OKComics. Passing BookType.LightNovel to a scrape that includes them is allowed; those sites log a "site does not stock LightNovel — skipping" message and return zero entries, while the rest of the sites in the same call run normally. Nothing lands in Errors — the skip is intentional, not a failure.


Getting Results

GetResults() returns the consolidated, deduped, sorted entries. GetResultUrls() returns the per-site landing URL used so callers can deep-link back into the source.

IReadOnlyList<EntryModel> results = scrape.GetResults();
IReadOnlyDictionary<Website, string> urls = scrape.GetResultUrls();
EntryModel

Each row is a public struct with four public fields:

Field Type Example
Entry string "Jujutsu Kaisen Vol 12"
Price string "$9.99" (display form with currency symbol)
StockStatus StockStatus IS, PO, BO, CS, OOS, NA
Website string "Crunchyroll" (the source site's TITLE constant)

Plus a ParsePrice() method that returns a decimal (handles currency prefixes/suffixes and returns 0m for unparseable rows).

foreach (EntryModel row in scrape.GetResults())
{
    if (row.StockStatus == StockStatus.IS && row.ParsePrice() < 10m)
    {
        Console.WriteLine($"{row.Entry} — {row.Price} at {row.Website}");
    }
}
Built-in renderers

Three convenience renderers ship with the library — all three take the same isAsciiTable, title, bookType, includeLinks parameters.

// To console
scrape.PrintResultsToConsole(isAsciiTable: true,
    title: "jujutsu kaisen", bookType: BookType.Manga);

// To a logger at a chosen level
logger.PrintResults(scrape, LogLevel.Information, isAsciiTable: true,
    title: "jujutsu kaisen", bookType: BookType.Manga);

// To a file
scrape.PrintResultsToFile("FinalData.txt", isAsciiTable: true,
    title: "jujutsu kaisen", bookType: BookType.Manga);

isAsciiTable: true → table view, useful for human review:

Title: "jujutsu kaisen"
BookType: Manga
Region: America
┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Title                 ┃ Price ┃ Status ┃ Website                 ┃
┣━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━╋━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━┫
┃ Jujutsu Kaisen Vol 1  ┃ $7.49 ┃ IS     ┃ InStockTrades           ┃
┃ Jujutsu Kaisen Vol 2  ┃ $7.49 ┃ IS     ┃ InStockTrades           ┃
┃ Jujutsu Kaisen Vol 3  ┃ $8.98 ┃ IS     ┃ RobertsAnimeCornerStore ┃
┃ Jujutsu Kaisen Vol 4  ┃ $7.49 ┃ IS     ┃ InStockTrades           ┃
┗━━━━━━━━━━━━━━━━━━━━━━━┻━━━━━━━┻━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━┛
Links:
InStockTrades => https://www.instocktrades.com/search?title=jujutsu+kaisen
RobertsAnimeCornerStore => https://www.animecornerstore.com/jjkmgrno.html

isAsciiTable: false (the default) → one bracketed line per row, suitable for piping into log aggregators or diff tooling:

[Jujutsu Kaisen Vol 1, $7.49, IS, InStockTrades]
[Jujutsu Kaisen Vol 2, $7.49, IS, InStockTrades]
[Jujutsu Kaisen Vol 3, $8.98, IS, RobertsAnimeCornerStore]
[Jujutsu Kaisen Vol 4, $7.49, IS, InStockTrades]
[InStockTrades,https://www.instocktrades.com/search?title=jujutsu+kaisen]
[RobertsAnimeCornerStore,https://www.animecornerstore.com/jjkmgrno.html]

Error Handling

A single site failing never aborts the rest of the scrape — per-site failures land in scrape.Errors (IReadOnlyDictionary<Website, Exception>). Catastrophic failures (Playwright browser launch, cancellation) propagate as thrown exceptions.

try
{
    await scrape.InitializeScrapeAsync(title, bookType, sites, ct);
}
catch (OperationCanceledException)
{
    // User cancelled — re-throw or surface as cancelled.
    throw;
}
catch (ScrapeBrowserLaunchException ex)
{
    // Host setup problem — no Playwright sites can run until fixed.
    logger.LogCritical(ex, "Playwright browser failed to launch");
}
catch (ArgumentException ex)
{
    // Bad input (empty title, sites not valid for the current region).
}

foreach ((Website site, Exception ex) in scrape.Errors)
{
    logger.LogWarning(ex, "{Site} failed for {Title}", site, title);
}

Exception hierarchy:

Type When
ScrapeException Abstract base — catch this to handle every library failure.
ScrapeBrowserLaunchException Playwright browser couldn't launch. Thrown from InitializeScrapeAsync.
SiteScrapeException A single site's scrape threw. Recorded in Errors, never thrown.

Full contract details live in docs/ErrorHandling.md.


Reachability Probes

Sites occasionally go down (DNS issues, maintenance, CDN outages). Use IsSiteAvailableAsync / CheckSitesAvailableAsync to short-circuit known-down sites instead of waiting on the scrape's per-site timeout.

// Single site
bool up = await scrape.IsSiteAvailableAsync(Website.MangaMate);

// Many at once (runs in parallel)
IReadOnlyDictionary<Website, bool> status = await scrape.CheckSitesAvailableAsync([
    Website.Crunchyroll, Website.MangaMate, Website.SciFier
]);

HashSet<Website> reachable = [.. status.Where(p => p.Value).Select(p => p.Key)];
if (reachable.Count > 0)
{
    await scrape.InitializeScrapeAsync(title, BookType.Manga, reachable);
}

Returns true when the host resolves and the server answers — including auth-gated (401/403) and CDN-challenged (Cloudflare) responses. DNS failures, connection refusals, timeouts, and 5xx returns false.

Auto-Skipping Unavailable Sites

Set SkipUnavailableSites = true to have InitializeScrapeAsync run the pre-flight check itself and silently drop any site that doesn't respond. Skipped sites are still recorded in Errors as SiteUnavailableException so the caller can see what was omitted without writing the filter manually.

scrape.SkipUnavailableSites = true;

await scrape.InitializeScrapeAsync(title, BookType.Manga, [
    Website.Crunchyroll, Website.MangaMate, Website.SciFier
]);

foreach ((Website site, Exception ex) in scrape.Errors)
{
    if (ex is SiteUnavailableException)
    {
        logger.LogInformation("{Site} was skipped (unreachable)", site);
    }
}

Enum Reference

The enums you'll touch most often. Full XML docs ship with the package so IDE tooltips have the same details.

BookType — what to search for. Picked per scrape call.

Value Meaning
Manga Graphic-novel format.
LightNovel Prose light-novel format. Manga-only sites silently skip — see Manga-only sites.

Region — [Flags] enum but one region per scrape (combining throws).

Value Notes
America Default.
Australia
Britain
Canada Currently SciFier only.
Europe Currently SciFier only.
Japan No working sites yet.

StockStatus — appears on every EntryModel.

Value Meaning
IS In stock — purchasable and shippable now.
PO Pre-order — committed, ships at release.
BO Backorder — site will fulfill when restocked.
CS Coming soon — announced, no pre-order yet.
OOS Out of stock — listed but currently unavailable.
NA Status not exposed by the site.

StockStatusFilter — passed to the MasterScrape constructor. Each one is a static StockStatus[] of statuses to drop from results.

Value Drops
EXCLUDE_NONE_FILTER Nothing — keeps every entry.
EXCLUDE_PO_FILTER PO
EXCLUDE_OOS_FILTER OOS
EXCLUDE_BO_FILTER BO
EXCLUDE_OOS_AND_PO_FILTER OOS, PO
EXCLUDE_OOS_AND_BO_FILTER OOS, BO
EXCLUDE_PO_AND_BO_FILTER PO, BO
EXCLUDE_ALL_FILTER PO, OOS, BO

Browser — Playwright channel for JS-rendered sites. Ignored when the scrape includes only HTML-only sites.

Value Channel
Chrome chrome
Edge msedge (default)
FireFox firefox

Membership — [Flags], combine with \|. Tag sites where the user holds a membership account; the scraper picks the discounted price column for those sites only.

Value Site
None Default — public price columns only.
BooksAMillion Millionaire's Club pricing.
KinokuniyaUSA Bookweb pricing.

Stock-Aware Deduplication

The same volume often appears multiple times within a single site's results (reprints, new-printing variants, edition markers) and again across sites. The merge pass picks survivors by availability first, then price:

  1. Lowest availability rank wins: IS > PO > BO > CS > OOS > NA.
  2. Ties break by lowest EntryModel.ParsePrice().

This deliberately drops a cheaper-but-out-of-stock listing in favor of a more expensive listing the user can actually buy, which is the common shape for Diamond-distributed manga where old printings linger as OOS rows after a reprint replaces them.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
6.0.0 141 6/27/2026
5.0.2 310 9/20/2025
4.0.2 225 1/21/2025
3.1.1 266 9/16/2024
3.1.0 231 9/5/2024
3.0.1 239 6/23/2024
2.1.2 236 3/1/2024
2.1.0 247 2/29/2024
2.0.0 232 2/29/2024
1.3.2 245 2/24/2024
1.3.1 238 2/24/2024
1.3.0 232 2/24/2024
1.2.1 252 2/3/2024
Loading failed