[an error occurred while processing this directive]

← Back to Library Pop-Up Tools

Suspicious Books Screener (suspicious_books_screener.py)

This tool adds a small suspicious_ai_like_score (0–3) and a short note to help you spot titles that may be mass-produced or very low-effort, based on generic titles and templated descriptions. It cannot prove a book is AI-generated.

How It Works (In Plain Language)

Your CSV
Title / Author / Description / …
Script reads each row
Title checks
Looks for generic phrases and very long, keyword-heavy titles.
Description checks
Looks for template phrases like "In this book you will learn…".
Author checks
Optionally notes if the same author appears on many titles.
Combines results into
score 0–3 + notes
Writes a new CSV with
suspicious_ai_like_score and suspicious_notes
  • Looks at key text fields such as title, author, and description/summary in your CSV.
  • Checks the title for red-flag phrases like "Ultimate Guide", "Comprehensive Guide", "for Beginners and Experts" and very long, keyword-stuffed titles.
  • Checks the description for template-like phrases such as "In this book you will learn…", "This comprehensive guide will…" and lots of repeated "you will…" lines.
  • Optionally looks at author volume and adds a small extra score if the same author appears on many titles in the same file.
  • Combines these into a 0–3 score and a short note explaining why it flagged the record (for example "generic/long title; repetitive description").
  • Writes a new CSV with two extra columns: suspicious_ai_like_score and suspicious_notes, so you can sort and review in your own way.

How to Use

  1. Try it with the included sample_books.csv first.
  2. Open Terminal and run: cd ~/Desktop/library_pop_up_tools
  3. Then run: python suspicious_books_screener.py sample_books.csv sample_books_with_flags.csv
  4. Open sample_books_with_flags.csv and sort by suspicious_ai_like_score.
  5. For your own export, replace sample_books.csv with your own CSV file name.
library_pop_up_tools % python suspicious_books_screener.py sample_books.csv sample_books_with_flags.csv
Screened 4 books.
Score 0: 2 titles
Score 3: 2 titles
Flagged file written to: sample_books_with_flags.csv

Optional: Adjust What It Watches For

You can use this tool exactly as-is and never touch the code. The list below is just a set of "phrases to watch for": like settings. Only change them if you want to look for something specific.

Key phrases it looks for (simplified view):

GENERIC_PHRASES_TITLE = [
    "ultimate guide",
    "made easy",
    "using chatgpt",
    "passive income",
    # add your own...
]

GENERIC_PHRASES_DESC = [
    "in this book you will learn",
    "no prior knowledge required",
    "financial freedom",
    "chatgpt prompts",
    # add your own...
]

To add a new phrase later, you can open suspicious_books_screener.py, find these two lists near the top, add a new line like "instant results",, save, and run the tool again.

Full Python Source (Optional)

Click to show the full script
#!/usr/bin/env python3
"""
suspicious_books_screener.py

Pop-up tool to flag books that look "mass-produced" or low-effort,
based on simple, transparent rules. It does NOT prove that a book is
AI-generated. It only surfaces titles that might deserve closer review.

Usage:
    python suspicious_books_screener.py books.csv books_with_flags.csv
"""

import csv
import sys
from collections import Counter
from pathlib import Path


GENERIC_PHRASES_TITLE = [
    "ultimate guide",
    "comprehensive guide",
    "for beginners",
    "for beginners and experts",
    "step by step",
    "step-by-step",
    "the complete guide",
    "the essential guide",
]

GENERIC_PHRASES_DESC = [
    "in this book you will learn",
    "in this book, you will learn",
    "this book will teach you",
    "this comprehensive guide will",
    "by the end of this book",
    "whether you are a beginner or an expert",
    "this book covers everything you need to know",
]


def score_title(title: str) -> int:
    if not title:
        return 0
    t = title.lower()
    score = 0
    if len(title) > 120:
        score += 1
    matches = sum(1 for phrase in GENERIC_PHRASES_TITLE if phrase in t)
    if matches >= 1:
        score += 1
    if "," in title and ":" in title and len(title.split()) > 15:
        score += 1
    return score


def score_description(desc: str) -> int:
    if not desc:
        return 0
    d = desc.lower()
    score = 0
    matches = sum(1 for phrase in GENERIC_PHRASES_DESC if phrase in d)
    if matches >= 2:
        score += 2
    elif matches == 1:
        score += 1
    if desc.count("you will") >= 3:
        score += 1
    if len(desc) > 0 and len(set(desc.split())) < len(desc.split()) * 0.5:
        score += 1
    return score


def count_authors(rows: list[dict], author_key: str) -> Counter:
    counter: Counter = Counter()
    for row in rows:
        author = row.get(author_key, "").strip()
        if author:
            counter[author] += 1
    return counter


def detect_author_volume_flags(rows: list[dict], author_key: str) -> dict:
    result: dict[int, int] = {}
    if not author_key:
        return result
    counts = count_authors(rows, author_key)
    high_volume_authors = {a for a, c in counts.items() if c >= 10}
    if not high_volume_authors:
        return result
    for idx, row in enumerate(rows):
        author = row.get(author_key, "").strip()
        if author in high_volume_authors:
            result[idx] = 1
    return result


def choose_key(row: dict, candidates: list[str]) -> str:
    for name in candidates:
        for key in row.keys():
            if key.lower() == name.lower():
                return key
    return ""


def screen_books(input_path: Path, output_path: Path) -> None:
    with input_path.open(newline="", encoding="utf-8-sig") as infile:
        reader = csv.DictReader(infile)
        rows = list(reader)

    if not rows:
        print("No rows found in input file.")
        return

    sample_row = rows[0]
    title_key = choose_key(sample_row, ["title", "book_title"])
    author_key = choose_key(sample_row, ["author", "author_name"])
    desc_key = choose_key(sample_row, ["description", "desc", "summary", "blurb"])

    author_volume_scores = detect_author_volume_flags(rows, author_key)

    fieldnames = list(sample_row.keys())
    for extra in ("suspicious_ai_like_score", "suspicious_notes"):
        if extra not in fieldnames:
            fieldnames.append(extra)

    suspicious_counts = Counter()

    with output_path.open("w", newline="", encoding="utf-8") as outfile:
        writer = csv.DictWriter(outfile, fieldnames=fieldnames)
        writer.writeheader()

        for idx, row in enumerate(rows):
            for key in row:
                if row[key] is None:
                    row[key] = ""

            title = row.get(title_key, "")
            desc = row.get(desc_key, "")

            score = 0
            notes: list[str] = []

            t_score = score_title(title)
            if t_score:
                score += t_score
                notes.append("generic/long title")

            d_score = score_description(desc)
            if d_score:
                score += d_score
                notes.append("repetitive / template-like description")

            extra = author_volume_scores.get(idx, 0)
            if extra:
                score += extra
                notes.append("author appears on many titles")

            if score > 3:
                score = 3

            row["suspicious_ai_like_score"] = str(score)
            row["suspicious_notes"] = "; ".join(sorted(set(notes))) if score > 0 else ""
            suspicious_counts[score] += 1
            writer.writerow(row)

    print(f\'Screened {len(rows)} books.")
    for s in sorted(suspicious_counts.keys()):
        print(f\'Score {s}: {suspicious_counts[s]} titles")
    print("Higher scores simply mean: worth a closer human look.")


def main(argv: list[str]) -> int:
    if len(argv) != 3:
        print("Usage: python suspicious_books_screener.py books.csv books_with_flags.csv")
        return 1
    input_path = Path(argv[1]).expanduser()
    output_path = Path(argv[2]).expanduser()
    if not input_path.exists():
        print(f\'Input file not found: {input_path}")
        return 1
    screen_books(input_path, output_path)
    print(f\'Flagged file written to: {output_path}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main(sys.argv))

← Back to all tools

[an error occurred while processing this directive]