Skills Plugins MCP Prompt Model 博客 我的中心
内容创作 #api #web

web-scraping

Web scraping with anti-bot bypass, content extraction, undocumented APIs and poison pill detection. Use when extracting content from websites, handling paywalls, implementing scraping cascades or processing social media. Covers requests, trafilatura, Playwright with stealth mode, yt-dlp and instaloader patterns.

DeepseekModel 官方收录技能 质量 优秀 · 90 v1.0.0

获取

https://deepseekmodel.com/api/download.php?id=maxmiksa-auto-company-claude-skills-web-scraping-skill-md&format=skill
下载 .skill 标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用
.skill 文件中 system_prompt 字段的实际内容。
name web-scraping description Web scraping with anti-bot bypass, content extraction, undocumented APIs and poison pill detection. Use when extracting content from websites, handling paywalls, implementing scraping cascades or processing social media. Covers requests, trafilatura, Playwright with stealth mode, yt-dlp and instaloader patterns. Web scraping methodology Patterns for reliable, ethical web scraping with fallback strategies and anti-bot handling. Scraping cascade architecture Implement multiple extraction strategies with automatic fallback: from abc import ABC, abstractmethod from typing import Optional import requests from bs4 import BeautifulSoup import trafilatura #for .py files from playwright.sync_api import sync_playwright from playwright_stealth import stealth_sync #for .ipynb files import asyncio from playwright.async_api import async_playwright class ScrapingResult : def __init__ ( self, content: str , title: str , method: str ): self .content = content self .title = title self .method = method # Track which method succeeded class Scraper ( ABC ): @abstractmethod def fetch ( self, url: str ) -> Optional [ScrapingResult]: ... class TrafilaturaСscraper ( Scraper ): """Fast, lightweight extraction for standard articles.""" def fetch ( self, url: str ) -> Optional [ScrapingResult]: try : downloaded = trafilatura.fetch_url(url) if not downloaded: return None content = trafilatura.extract( downloaded, include_comments= False , include_tables= True , favor_recall= True ) if not content or len (content) < 100 : return None # Extract title separately soup = BeautifulSoup(downloaded, 'html.parser' ) title = soup.find( 'title' ) title_text = title.get_text() if title else '' return ScrapingResult(content, title_text, 'trafilatura' ) except Exception: return None class RequestsScraper ( Scraper ): """HTTP requests with rotating user agents.""" USER_AGENTS = [ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' , 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36' , 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36' , ] def fetch ( self, url: str ) -> Optional [ScrapingResult]: import random headers = { 'User-Agent' : random.choice( self .USER_AGENTS), 'Accept' : 'text/html,application/xhtml+xml' , 'Accept-Language' : 'en-US,en;q=0.9' , } try : response = requests.get(url, headers=headers, timeout= 30 ) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser' ) # Remove script/style elements for element in soup([ 'script' , 'style' , 'nav' , 'footer' , 'aside' ]): element.decompose() # Find main content main = soup.find( 'main' ) or soup.find( 'article' ) or soup.find( 'body' ) content = main.get_text(separator= '\n' , strip= True ) if main else '' title = soup.find( 'title' ) title_text = title.get_text() if title else '' if len (content) < 100 : return None return ScrapingResult(content, title_text, 'requests' ) except Exception: return None class PlaywrightScraper ( Scraper ): """Heavy JavaScript rendering with stealth mode for anti-bot bypass.""" def fetch ( self, url: str ) -> Optional [ScrapingResult]: try : with sync_playwright() as p: browser = p.chromium.launch(headless= True ) context = browser.new_context( viewport={ 'width' : 1920 , 'height' : 1080 }, user_agent= 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) page = context.new_page() # Apply stealth to avoid detection stealth_sync(page) page.goto(url, wait_until= 'networkidle' , timeout= 60000 ) # Wait for content to load page.wait_for_timeout( 2000 ) # Extract content content = page.evaluate( '''() => { const article = document.querySelector('article, main, .content, #content'); return article ? article.innerText : document.body.innerText; }''' ) title = page.title() browser.close() if len (content) < 100 : return None return ScrapingResult(content, title, 'playwright' ) except Exception: return None class PlaywrightScraperAsync : """Async Playwright scraper for Jupyter notebooks (.ipynb files). Jupyter notebooks run their own event loop, so sync Playwright won't work. Use this async version with `await` in notebook cells. """ async def fetch ( self, url: str ) -> Optional [ScrapingResult]: try : async with async_playwright() as p: browser = await p.chromium.launch(headless= True ) context = await browser.new_context( viewport={ 'width' : 1920 , 'height' : 1080 }, user_agent= 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' ) page = await context.new_page() # Note: playwright-stealth async version # from playwright_stealth import stealth_async # await stealth_async(page) await page.goto(url, wait_until= 'networkidle' , timeout= 60000 ) # Wait for content to load await page.wait_for_timeout( 2000 ) # Extract content content = await page.evaluate( '''() => { const article = document.querySelector('article, main, .content, #content'); return article ? article.innerText : document.body.innerText; }''' ) title = await page.title() await browser.close() if len (content) < 100 : return None return ScrapingResult(content, title, 'playwright_async' ) except Exception: return None # Usage in Jupyter notebook cells: # scraper = PlaywrightScraperAsync() # result = await scraper.fetch('https://example.com') class ScrapingCascade : """Try multiple scrapers in order until one succeeds.""" def __init__ ( self ): self .scrapers = [ TrafilaturaСscraper(), RequestsScraper(), PlaywrightScraper(), ] def fetch ( self, url: str ) -> Optional [ScrapingResult]: for scraper in self .scrapers: result = scraper.fetch(url) if result: return result return None Undocumented APIs Finding undocumented APIs Use browser developer tools to discover APIs: Open developer tools (right-click → Inspect, or F12) Go to the Network tab to monitor all requests Filter by Fetch/XHR to show only API calls Trigger the action you want to capture (search, scroll, click) Analyze the response — usually JSON with key-value pairs Copy as cURL (right-click the request) Convert to code using curlconverter.com Stripping down API requests When you copy a cURL from dev tools, it includes many parameters. Strip it down by: Remove unnecessary cookies — test without them first Keep authentication tokens if required Identify the input parameters you can modify (like prefix for search terms) Test parameter values — some expire, so periodically verify Example: Reverse-engineering an autocomplete API import requests import time def search_suggestions ( keyword: str ) -> dict : """ Get autocompleted search suggestions from an undocumented API. Stripped down from browser dev tools capture. """ headers = { 'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:100.0) Gecko/20100101 Firefox/100.0' , 'Accept' : 'application/json, text/javascript, */*; q=0.01' , 'Accept-Language' : 'en-US,en;q=0.5' , } params = { 'prefix' : keyword, 'suggestion-type' : [ 'WIDGET' , 'KEYWORD' ], 'alias' : 'aps' , 'plain-mid' : '1' , } response = requests.get( 'https://completion.amazon.com/api/2017/suggestions' , params=params, headers=headers ) return response.json() # Collect suggestions for multiple keywords keywords = [ 'a' , 'b' , 'cookie' , 'sock' ] data = [] for keyword in keywords: suggestions = search_suggestions(keyword) suggestions[ 'search_word' ] = keyword # track seed keyword time.sleep( 1 ) # rate limit yourself data.extend(suggestions.get( 'suggestions' , [])) Source: Leon Yin, "Finding Undocumented APIs," Inspect Element , 2023 Poison pill detection Detect paywalls, anti-bot pages, and other failures: from dataclasses import dataclass from enum import Enum import re class PoisonPillType ( Enum ): PAYWALL = 'paywall' CAPTCHA = 'captcha' RATE_LIMIT = 'rate_limit' CLOUDFLARE = 'cloudflare' LOGIN_REQUIRED = 'login_required' NOT_FOUND = 'not_found' NONE = 'none' @dataclass class PoisonPillResult : detected: bool type : PoisonPillType confidence: float details: str class PoisonPillDetector : PATTERNS = { PoisonPillType.PAYWALL: [ r'subscribe to continue' , r'subscription required' , r'become a member' , r'sign up to read' , r'you\'ve reached your limit' , r'article limit reached' , ], PoisonPillType.CAPTCHA: [ r'verify you are human' , r'captcha' , r'robot verification' , r'prove you\'re not a robot' , ], PoisonPillType.RATE_LIMIT: [ r'too many requests' , r'rate limit exceeded' , r'slow down' , r'429' , ], PoisonPillType.CLOUDFLARE: [ r'checking your browser' , r'cloudflare' , r'ddos protection' , r'please wait while we verify' , ], PoisonPillType.LOGIN_REQUIRED: [ r'sign in to continue' , r'log in required' , r'create an account' , ], } PAYWALL_DOMAINS = { 'nytimes.com' : PoisonPillType.PAYWALL, 'wsj.com' : PoisonPillType.PAYWALL, 'washingtonpost.com' : PoisonPillType.PAYWALL, 'ft.com' : PoisonPillType.PAYWALL, 'bloomberg.com' : PoisonPillType.PAYWALL, } def detect ( self, url: str , content: str , status_code: int = 200 ) -> PoisonPillResult: # Check status code if status_code == 429 : return PoisonPillResult( True , PoisonPillType.RATE_LIMIT, 1.0 , 'HTTP 429' ) if status_code == 403 : return PoisonPillResult( True , PoisonPillType.CLOUDFLARE, 0.8 , 'HTTP 403' ) if status_code == 404 : return PoisonPillResult( True , PoisonPillType.NOT_FOUND, 1.0 , 'HTTP 404' ) # Check known paywall domains from urllib.parse import urlparse domain = urlparse(url).netloc.replace( 'www.' , '' ) for paywall_domain, pill_type in self .PAYWALL_DOMAINS.items(): if paywall_domain in domain: # Check if content is suspiciously short (paywall truncation) if len (content) < 500 : return PoisonPillResult( True , pill_type, 0.9 , f'Short content from {domain} ' ) # Pattern matching content_lower = content.lower() for pill_type, patterns in self .PATTERNS.items(): for pattern in patterns: if re.search(pattern, content_lower): return PoisonPillResult( True , pill_type, 0.7 , f'Pattern match: {pattern} ' ) return PoisonPillResult( False , PoisonPillType.NONE, 0.0 , '' ) Social media scraping YouTube with yt-dlp import yt_dlp from pathlib import Path def download_video_metadata ( url: str ) -> dict : """Extract metadata without downloading video.""" ydl_opts = { 'skip_download' : True , 'quiet' : True , 'no_warnings' : True , } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download= False ) return { 'title' : info.get( 'title' ), 'description' : info.get( 'description' ), 'duration' : info.get( 'duration' ), 'upload_date' : info.get( 'upload_date' ), 'view_count' : info.get( 'view_count' ), 'channel' : info.get( 'channel' ), 'thumbnail' : info.get( 'thumbnail' ), } def download_video ( url: str , output_dir: Path, audio_only: bool = False ) -> Path: """Download video or audio.""" output_template = str (output_dir / '%(title)s.%(ext)s' ) ydl_opts = { 'outtmpl' : output_template, 'quiet' : True , } if audio_only: ydl_opts[ 'format' ] = 'bestaudio/best' ydl_opts[ 'postprocessors' ] = [{ 'key' : 'FFmpegExtractAudio' , 'preferredcodec' : 'mp3' , }] with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(url, download= True ) filename = ydl.prepare_filename(info) if audio_only: filename = filename.rsplit( '.' , 1 )[ 0 ] + '.mp3' return Path(filename) def get_transcript ( url: str ) -> list [ dict ]: """Extract auto-generated or manual subtitles.""" ydl_opts = { 'skip_download' : True , 'writesubtitles' : True , 'writeautomaticsub' : True , 'subtitleslangs' : [ 'en' ],
Agent 识别该技能的关键词,点击任意一个即可复制。

该技能未提供触发词。

下载的 .skill 包内含以下字段。
字段 说明
format格式标识(skill/v1)
skill_id技能唯一 ID
name技能名称
version版本号
description技能描述
category所属分类(数组)
trigger_words触发词列表
tags标签列表
source来源标识
source_url来源链接(本页地址)
exported_at导出时间(每次下载生成)
system_prompt系统提示词正文
model_config模型参数:provider / model / temperature / max_tokens / top_p
examples示例
install_guide各平台导入说明(Coze / Dify / Claude / 自定义框架)
同一份技能可按不同平台格式导出。
.skill 标准格式,含 system_prompt 与 model_config,导入任意 Agent 框架即可使用 下载
.skillpro 增强格式,额外含脚本 / 工具 / 依赖 / 钩子占位 下载
.json 纯 JSON 导出,只含 system_prompt 与模型参数 下载
Coze 带 frontmatter 的 Markdown,Coze 平台导入用 下载
Dify Dify DSL,创建应用后直接导入 下载

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。