Files
wehub-resource-sync 5621867692
Sync browser-act to plugin repo / sync (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:30:35 +08:00

368 lines
14 KiB
Python

"""
Amazon Competitor Analyzer Skill
===============================
Scrapes Amazon product data from ASINs using BrowserAct API
and performs surgical competitive analysis.
Author: OpenCode
Version: 1.0.0
"""
import os
import sys
import json
import time
import csv
import requests
from datetime import datetime
from typing import Dict, List, Optional, Any
from pathlib import Path
# Try to load .env file if python-dotenv is available
try:
from dotenv import load_dotenv
# Load .env from same directory as script
script_dir = Path(__file__).parent
env_file = script_dir / ".env"
if env_file.exists():
load_dotenv(env_file)
except ImportError:
# If python-dotenv not available, check .env manually
script_dir = Path(__file__).parent
env_file = script_dir / ".env"
if env_file.exists():
with open(env_file) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
key, value = line.split('=', 1)
os.environ.setdefault(key, value.strip())
# Configuration
BROWSERACT_API_KEY = os.getenv("BROWSERACT_API_KEY", "")
WORKFLOW_TEMPLATE_ID = "77814333389670716"
API_BASE_URL = "https://api.browseract.com/v2/workflow"
class AmazonCompetitorAnalyzer:
"""Main class for Amazon competitive analysis"""
def __init__(self, api_key: str = None, workflow_template_id: str = None):
"""Initialize the analyzer with API credentials"""
self.api_key = api_key or BROWSERACT_API_KEY
self.workflow_template_id = workflow_template_id or WORKFLOW_TEMPLATE_ID
self.headers = {
"Authorization": f"Bearer {self.api_key}"
}
def validate_asin(self, asin: str) -> bool:
"""Validate ASIN format"""
return len(asin) == 10 and asin.isalnum()
def extract_asins_from_text(self, text: str) -> List[str]:
"""Extract ASINs from user input text"""
import re
# Match 10-character alphanumeric strings starting with B0 or similar
asin_pattern = r'\b[B0][A-Z0-9]{9}\b'
asins = re.findall(asin_pattern, text.upper())
return list(set(asins)) # Remove duplicates
def submit_task(self, asin: str) -> Optional[str]:
"""Submit scraping task for a single ASIN"""
if not self.validate_asin(asin):
print(f"Invalid ASIN: {asin}")
return None
data = {
"workflow_template_id": self.workflow_template_id,
"input_parameters": [{"name": "ASIN", "value": asin}]
}
try:
response = requests.post(
f"{API_BASE_URL}/run-task-by-template",
json=data,
headers=self.headers,
timeout=30
)
if response.status_code == 200:
result = response.json()
task_id = result.get("id")
return task_id
else:
print(f"Failed to submit task for {asin}: {response.json().get('msg', 'Unknown error')}")
return None
except Exception as e:
print(f"Error submitting task for {asin}: {e}")
return None
def wait_for_task(self, task_id: str, timeout: int = 300) -> bool:
"""Wait for task completion"""
start_time = time.time()
while time.time() - start_time < timeout:
try:
response = requests.get(
f"{API_BASE_URL}/get-task-status?task_id={task_id}",
headers=self.headers,
timeout=10
)
if response.status_code == 200:
status = response.json().get("status")
if status == "finished":
return True
elif status in ["failed", "canceled"]:
return False
time.sleep(3)
except Exception:
time.sleep(5)
return False
def get_results(self, task_id: str) -> Optional[Dict]:
"""Get task results"""
try:
response = requests.get(
f"{API_BASE_URL}/get-task?task_id={task_id}",
headers=self.headers,
timeout=30
)
if response.status_code == 200:
return response.json()
except Exception as e:
print(f"Error getting results: {e}")
return None
def scrape_product(self, asin: str, wait_timeout: int = 300) -> Optional[Dict]:
"""Scrape a single product"""
# Submit task
task_id = self.submit_task(asin)
if not task_id:
return None
# Wait for completion
if not self.wait_for_task(task_id, wait_timeout):
return None
# Get results
results = self.get_results(task_id)
return results
def scrape_multiple_products(self, asins: List[str], delay: int = 5) -> Dict[str, Any]:
"""Scrape multiple products"""
results = {}
for asin in asins:
print(f"Processing: {asin}")
data = self.scrape_product(asin)
results[asin] = data
if delay > 0 and asin != asins[-1]:
time.sleep(delay)
return results
def analyze_competitive_position(self, products: Dict[str, Any]) -> Dict[str, Any]:
"""Analyze competitive positioning"""
analysis = {
"price_analysis": {},
"rating_analysis": {},
"market_leaders": {},
"opportunities": []
}
prices = []
ratings = []
for asin, data in products.items():
if data:
try:
product = data.get('results', {}).get('products', [{}])[0]
price = product.get('pricing', {}).get('current_price', 0)
rating = product.get('reviews', {}).get('average_rating', 0)
reviews = product.get('reviews', {}).get('total_count', 0)
brand = product.get('product_info', {}).get('brand', asin)
prices.append((asin, price, brand))
ratings.append((asin, rating, reviews, brand))
except Exception:
pass
# Sort by price
prices.sort(key=lambda x: x[1])
if prices:
analysis["price_analysis"]["lowest"] = prices[0]
analysis["price_analysis"]["highest"] = prices[-1]
analysis["price_analysis"]["range"] = prices[-1][1] - prices[0][1]
# Sort by rating
ratings.sort(key=lambda x: x[1], reverse=True)
if ratings:
analysis["rating_analysis"]["top_rated"] = ratings[0]
analysis["rating_analysis"]["by_volume"] = sorted(ratings, key=lambda x: x[2], reverse=True)
return analysis
def generate_csv_report(self, products: Dict[str, Any], output_path: str):
"""Generate CSV report"""
with open(output_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
'ASIN', 'Product Title', 'Brand', 'Price ($)', 'Original Price ($)',
'Discount (%)', 'Rating', 'Reviews Count', 'Weight', 'Features'
])
for asin, data in products.items():
if data:
try:
product = data.get('results', {}).get('products', [{}])[0]
writer.writerow([
asin,
product.get('product_info', {}).get('title', 'N/A')[:100],
product.get('product_info', {}).get('brand', 'N/A'),
product.get('pricing', {}).get('current_price', 'N/A'),
product.get('pricing', {}).get('original_price', 'N/A'),
product.get('pricing', {}).get('discount_percent', 'N/A'),
product.get('reviews', {}).get('average_rating', 'N/A'),
product.get('reviews', {}).get('total_count', 'N/A'),
product.get('specifications', {}).get('weight', 'N/A'),
', '.join(product.get('specifications', {}).get('features', [])[:5])
])
except Exception:
writer.writerow([asin, 'Error', '', '', '', '', '', '', '', ''])
else:
writer.writerow([asin, 'Failed', '', '', '', '', '', '', '', ''])
print(f"CSV report saved: {output_path}")
def generate_markdown_report(self, products: Dict[str, Any], output_path: str):
"""Generate comprehensive markdown report"""
with open(output_path, 'w', encoding='utf-8') as f:
f.write("# Amazon Competitive Analysis Report\n\n")
f.write(f"**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write(f"**Products Analyzed:** {len(products)}\n\n")
# Summary table
f.write("## Data Summary\n\n")
f.write("| ASIN | Brand | Price | Rating | Reviews |\n")
f.write("|------|-------|-------|--------|---------|\n")
for asin, data in products.items():
if data:
try:
product = data.get('results', {}).get('products', [{}])[0]
info = product.get('product_info', {})
pricing = product.get('pricing', {})
reviews = product.get('reviews', {})
f.write(f"| {asin} | {info.get('brand', 'N/A')} | "
f"${pricing.get('current_price', 'N/A')} | "
f"{reviews.get('average_rating', 'N/A')}/5 | "
f"{reviews.get('total_count', 'N/A'):,} |\n")
except Exception:
f.write(f"| {asin} | Error | - | - | - |\n")
else:
f.write(f"| {asin} | Failed | - | - | - |\n")
# Detailed analysis
f.write("\n## Detailed Analysis\n\n")
competitive_analysis = self.analyze_competitive_position(products)
f.write("### Price Positioning\n")
if competitive_analysis.get("price_analysis"):
pa = competitive_analysis["price_analysis"]
if "lowest" in pa:
f.write(f"- Lowest Price: {pa['lowest'][2]} at ${pa['lowest'][1]}\n")
if "highest" in pa:
f.write(f"- Highest Price: {pa['highest'][2]} at ${pa['highest'][1]}\n")
f.write("\n### Rating Leaders\n")
if competitive_analysis.get("rating_analysis"):
ra = competitive_analysis["rating_analysis"]
if "top_rated" in ra:
f.write(f"- Highest Rated: {ra['top_rated'][3]} at {ra['top_rated'][1]}/5\n")
f.write("\n---\n")
f.write(f"*Generated by Amazon Competitor Analyzer*\n")
print(f"Markdown report saved: {output_path}")
def generate_json_report(self, products: Dict[str, Any], output_path: str):
"""Generate JSON report"""
report_data = {
"generated_at": datetime.now().isoformat(),
"products_analyzed": len(products),
"products": products,
"analysis": self.analyze_competitive_position(products)
}
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(report_data, f, indent=2, ensure_ascii=False)
print(f"JSON report saved: {output_path}")
def analyze_asins(asins: List[str], output_dir: str = None) -> Dict[str, Any]:
"""Main function to analyze multiple ASINs"""
analyzer = AmazonCompetitorAnalyzer()
print(f"Analyzing {len(asins)} ASINs...")
# Scrape products
products = analyzer.scrape_multiple_products(asins)
# Generate reports if output directory specified
if output_dir:
os.makedirs(output_dir, exist_ok=True)
base_path = os.path.join(output_dir, "amazon_analysis")
analyzer.generate_csv_report(products, f"{base_path}.csv")
analyzer.generate_markdown_report(products, f"{base_path}.md")
analyzer.generate_json_report(products, f"{base_path}.json")
return products
def main():
"""CLI entry point"""
import argparse
parser = argparse.ArgumentParser(description="Amazon Competitor Analyzer")
parser.add_argument("asins", nargs="+", help="ASINs to analyze")
parser.add_argument("-o", "--output", default=".", help="Output directory")
parser.add_argument("-k", "--api-key", help="BrowserAct API key")
args = parser.parse_args()
# Run analysis
api_key = args.api_key or BROWSERACT_API_KEY
analyzer = AmazonCompetitorAnalyzer(api_key=api_key)
print(f"Analyzing {len(args.asins)} ASINs...")
products = analyzer.scrape_multiple_products(args.asins)
if args.output:
os.makedirs(args.output, exist_ok=True)
base_path = os.path.join(args.output, "amazon_analysis")
analyzer.generate_csv_report(products, f"{base_path}.csv")
analyzer.generate_markdown_report(products, f"{base_path}.md")
analyzer.generate_json_report(products, f"{base_path}.json")
print(f"\nAnalysis complete! Analyzed {len(args.asins)} products.")
if __name__ == "__main__":
main()