Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Extract clean article content from URLs (blog posts, articles, tutorials) and save as readable text. Use when user wants to download, extract, or save an article/blog post from a URL without ads, navigation, or clutter.
.claude/skills/nicepkg-article-extractor/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 179% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 166% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 129% | 0% |
| case-13 | ✗→✓ | ▲ Improved | 308% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 116% | 0% |
This skill extracts the main content from web articles and blog posts, removing navigation, ads, newsletter signups, and other clutter. Saves clean, readable text.
Activate when the user:
Check for article extraction tools in this order:
bashcommand -v reader
If not installed:
bashnpm install -g @mozilla/readability-cli # or npm install -g reader-cli
bashcommand -v trafilatura
If not installed:
bashpip3 install trafilatura
If no tools available, use basic curl + text extraction (less reliable but works)
bash# Extract article reader "URL" > article.txt
Pros:
bash# Extract article trafilatura --URL "URL" --output-format txt > article.txt # Or with more options trafilatura --URL "URL" --output-format txt --no-comments --no-tables > article.txt
Pros:
Options:
--no-comments: Skip comment sections--no-tables: Skip data tables--precision: Favor precision over recall--recall: Extract more content (may include some noise)bash# Download and extract basic content curl -s "URL" | python3 -c " from html.parser import HTMLParser import sys class ArticleExtractor(HTMLParser): def __init__(self): super().__init__() self.in_content = False self.content = [] self.skip_tags = {'script', 'style', 'nav', 'header', 'footer', 'aside'} self.current_tag = None def handle_starttag(self, tag, attrs): if tag not in self.skip_tags: if tag in {'p', 'article', 'main', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'}: self.in_content = True self.current_tag = tag def handle_data(self, data): if self.in_content and data.strip(): self.content.append(data.strip()) def get_content(self): return '\n\n'.join(self.content) parser = ArticleExtractor() parser.feed(sys.stdin.read()) print(parser.get_content()) " > article.txt
Note: This is less reliable but works without dependencies.
Extract title for filename:
bash# reader outputs markdown with title at top TITLE=$(reader "URL" | head -n 1 | sed 's/^# //')
bash# Get metadata including title TITLE=$(trafilatura --URL "URL" --json | python3 -c "import json, sys; print(json.load(sys.stdin)['title'])")
bashTITLE=$(curl -s "URL" | grep -oP '<title>\K[^<]+' | sed 's/ - .*//' | sed 's/ | .*//')
Clean title for filesystem:
bash# Get title TITLE="Article Title from Website" # Clean for filesystem (remove special chars, limit length) FILENAME=$(echo "$TITLE" | tr '/' '-' | tr ':' '-' | tr '?' '' | tr '"' '' | tr '<' '' | tr '>' '' | tr '|' '-' | cut -c 1-100 | sed 's/ *$//') # Add extension FILENAME="${FILENAME}.txt"
bashARTICLE_URL="https://example.com/article" # Check for tools if command -v reader &> /dev/null; then TOOL="reader" echo "Using reader (Mozilla Readability)" elif command -v trafilatura &> /dev/null; then TOOL="trafilatura" echo "Using trafilatura" else TOOL="fallback" echo "Using fallback method (may be less accurate)" fi # Extract article case $TOOL in reader) # Get content reader "$ARTICLE_URL" > temp_article.txt # Get title (first line after # in markdown) TITLE=$(head -n 1 temp_article.txt | sed 's/^# //') ;; trafilatura) # Get title from metadata METADATA=$(trafilatura --URL "$ARTICLE_URL" --json) TITLE=$(echo "$METADATA" | python3 -c "import json, sys; print(json.load(sys.stdin).get('title', 'Article'))") # Get clean content trafilatura --URL "$ARTICLE_URL" --output-format txt --no-comments > temp_article.txt ;; fallback) # Get title TITLE=$(curl -s "$ARTICLE_URL" | grep -oP '<title>\K[^<]+' | head -n 1) TITLE=${TITLE%% - *} # Remove site name TITLE=${TITLE%% | *} # Remove site name (alternate) # Get content (basic extraction) curl -s "$ARTICLE_URL" | python3 -c " from html.parser import HTMLParser import sys class ArticleExtractor(HTMLParser): def __init__(self): super().__init__() self.in_content = False self.content = [] self.skip_tags = {'script', 'style', 'nav', 'header', 'footer', 'aside', 'form'} def handle_starttag(self, tag, attrs): if tag not in self.skip_tags: if tag in {'p', 'article', 'main'}: self.in_content = True if tag in {'h1', 'h2', 'h3'}: self.content.append('\n') def handle_data(self, data): if self.in_content and data.strip(): self.content.append(data.strip()) def get_content(self): return '\n\n'.join(self.content) parser = ArticleExtractor() parser.feed(sys.stdin.read()) print(parser.get_content()) " > temp_article.txt ;; esac # Clean filename FILENAME=$(echo "$TITLE" | tr '/' '-' | tr ':' '-' | tr '?' '' | tr '"' '' | tr '<>' '' | tr '|' '-' | cut -c 1-80 | sed 's/ *$//' | sed 's/^ *//') FILENAME="${FILENAME}.txt" # Move to final filename mv temp_article.txt "$FILENAME" # Show result echo "✓ Extracted article: $TITLE" echo "✓ Saved to: $FILENAME" echo "" echo "Preview (first 10 lines):" head -n 10 "$FILENAME"
1. Tool not installed
2. Paywall or login required
3. Invalid URL
4. No content extracted
5. Special characters in title
/, :, ?, ", <, >, |- or remove1. Use reader for most articles
2. Use trafilatura for:
3. Fallback method limitations:
4. Check extraction quality:
Simple extraction:
bash# User: "Extract https://example.com/article" reader "https://example.com/article" > temp.txt TITLE=$(head -n 1 temp.txt | sed 's/^# //') FILENAME="$(echo "$TITLE" | tr '/' '-').txt" mv temp.txt "$FILENAME" echo "✓ Saved to: $FILENAME"
With error handling:
bashif ! reader "$URL" > temp.txt 2>/dev/null; then if command -v trafilatura &> /dev/null; then trafilatura --URL "$URL" --output-format txt > temp.txt else echo "Error: Could not extract article. Install reader or trafilatura." exit 1 fi fi
Display to user:
Ask if needed:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 7,380 | 43,624 | +491% | 1 | 1 | 0% | 1,258 | 3,637 | +189% | 0 | 0 | — |
case-02 | fail→fail | 8,890 | 5,612 | -37% | 1 | 1 | 0% | 1,523 | 2,880 | +89% | 0 | 0 | — |
case-03 | pass→fail | 8,759 | 5,710 | -35% | 1 | 1 | 0% | 1,552 | 2,872 | +85% | 0 | 0 | — |
case-04 | pass→pass | 17,604 | 10,553 | -40% | 1 | 1 | 0% | 3,131 | 4,413 | +41% | 0 | 0 | — |
case-05 | pass→pass | 11,575 | 13,183 | +14% | 1 | 1 | 0% | 2,045 | 4,229 | +107% | 0 | 0 | — |
case-06 | pass→pass | 10,687 | 6,113 | -43% | 1 | 1 | 0% | 1,625 | 3,522 | +117% | 0 | 0 | — |
case-07 | fail→pass | 8,794 | 7,472 | -15% | 1 | 1 | 0% | 1,394 | 3,888 | +179% | 0 | 0 | — |
case-08 | fail→pass | 7,564 | 3,279 | -57% | 1 | 1 | 0% | 1,219 | 3,239 | +166% | 0 | 0 | — |
case-09 | fail→fail | 14,175 | 18,483 | +30% | 1 | 1 | 0% | 2,729 | 6,295 | +131% | 0 | 0 | — |
case-10 | fail→pass | 9,211 | 7,251 | -21% | 1 | 1 | 0% | 1,752 | 4,008 | +129% | 0 | 0 | — |
case-11 | pass→pass | 10,506 | 6,769 | -36% | 1 | 1 | 0% | 1,749 | 3,945 | +126% | 0 | 0 | — |
case-12 | fail→fail | 6,969 | 2,740 | -61% | 1 | 1 | 0% | 1,034 | 3,130 | +203% | 0 | 0 | — |
case-13 | fail→pass | 4,639 | 3,810 | -18% | 1 | 1 | 0% | 727 | 2,966 | +308% | 0 | 0 | — |
case-14 | fail→pass | 10,707 | 4,864 | -55% | 1 | 1 | 0% | 1,542 | 3,337 | +116% | 0 | 0 | — |
case-15 | fail→pass | 9,694 | 4,628 | -52% | 1 | 1 | 0% | 1,410 | 3,379 | +140% | 0 | 0 | — |
case-16 | fail→pass | 10,242 | 2,456 | -76% | 1 | 1 | 0% | 1,613 | 3,016 | +87% | 0 | 0 | — |
case-17 | fail→pass | 8,832 | 3,011 | -66% | 1 | 1 | 0% | 1,321 | 3,104 | +135% | 0 | 0 | — |
case-18 | pass→pass | 8,958 | 3,219 | -64% | 1 | 1 | 0% | 1,433 | 3,183 | +122% | 0 | 0 | — |
case-19 | pass→pass | 10,943 | 4,689 | -57% | 1 | 1 | 0% | 1,476 | 3,361 | +128% | 0 | 0 | — |
case-20 | fail→pass | 5,389 | 1,363 | -75% | 1 | 1 | 0% | 828 | 2,776 | +235% | 0 | 0 | — |
case-21 | fail→pass | 15,074 | 2,566 | -83% | 1 | 1 | 0% | 2,232 | 2,999 | +34% | 0 | 0 | — |
case-22 | pass→pass | 11,158 | 4,398 | -61% | 1 | 1 | 0% | 1,663 | 3,326 | +100% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 19 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +41 percentage points is the difference between those two pass rates over the 19 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.