mirror of
https://git.mirrors.martin98.com/https://github.com/mendableai/firecrawl
synced 2025-04-18 12:09:42 +08:00
Add example/Deep-research Apartment finder
This commit is contained in:
parent
6a6199eb4b
commit
87539aaf16
5
examples/deep-research-apartment-finder/.env.example
Normal file
5
examples/deep-research-apartment-finder/.env.example
Normal file
@ -0,0 +1,5 @@
|
||||
# Firecrawl API key (get from https://firecrawl.dev)
|
||||
FIRECRAWL_API_KEY=your_firecrawl_api_key_here
|
||||
|
||||
# Anthropic API key (get from https://console.anthropic.com)
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
55
examples/deep-research-apartment-finder/README.md
Normal file
55
examples/deep-research-apartment-finder/README.md
Normal file
@ -0,0 +1,55 @@
|
||||
# Apartment Finder CLI
|
||||
|
||||
A command-line tool that uses Firecrawl's Deep Research API and Anthropic's Claude 3.7 to find and analyze apartment listings based on your preferences.
|
||||
|
||||
## Features
|
||||
|
||||
- Interactive input for apartment search preferences
|
||||
- Searches apartments by location, budget, bedrooms, and amenities
|
||||
- Automatically researches apartment listings across multiple websites
|
||||
- Uses AI to analyze and extract the top 3 options
|
||||
- Provides detailed information including price, location, features, and pros/cons
|
||||
- Option to save results as JSON
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone this repository:
|
||||
|
||||
```
|
||||
git clone <repository-url>
|
||||
cd apartment-finder
|
||||
```
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
```
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Set up API keys:
|
||||
- Copy `.env.example` to `.env`
|
||||
- Fill in your Firecrawl API key from [firecrawl.dev](https://firecrawl.dev)
|
||||
- Fill in your Anthropic API key from [console.anthropic.com](https://console.anthropic.com)
|
||||
|
||||
## Usage
|
||||
|
||||
Run the script and follow the interactive prompts:
|
||||
|
||||
```bash
|
||||
python apartment_finder.py
|
||||
```
|
||||
|
||||
The script will prompt you for:
|
||||
|
||||
- Location (city or neighborhood)
|
||||
- Budget (maximum monthly rent)
|
||||
- Number of bedrooms
|
||||
- Desired amenities
|
||||
|
||||
After searching and analyzing, the tool will display the top apartment options and offer to save the results to a JSON file.
|
||||
|
||||
## Notes
|
||||
|
||||
- The search process may take a few minutes due to the deep research API.
|
||||
- Results will vary based on available apartment listings at the time of search.
|
||||
- API usage may incur costs depending on your Firecrawl and Anthropic subscription plans.
|
293
examples/deep-research-apartment-finder/apartment_finder.py
Normal file
293
examples/deep-research-apartment-finder/apartment_finder.py
Normal file
@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from typing import Dict, List, Any
|
||||
import anthropic
|
||||
from firecrawl import FirecrawlApp
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Define colors for terminal output
|
||||
class Colors:
|
||||
CYAN = '\033[96m'
|
||||
YELLOW = '\033[93m'
|
||||
GREEN = '\033[92m'
|
||||
RED = '\033[91m'
|
||||
MAGENTA = '\033[95m'
|
||||
BLUE = '\033[94m'
|
||||
BOLD = '\033[1m'
|
||||
RESET = '\033[0m'
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
FIRECRAWL_API_KEY = os.getenv("FIRECRAWL_API_KEY")
|
||||
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
|
||||
|
||||
if not FIRECRAWL_API_KEY or not ANTHROPIC_API_KEY:
|
||||
print(f"{Colors.RED}Error: API keys not found. Please set FIRECRAWL_API_KEY and ANTHROPIC_API_KEY environment variables.{Colors.RESET}")
|
||||
print(f"{Colors.YELLOW}You can create a .env file with these variables or set them in your shell.{Colors.RESET}")
|
||||
sys.exit(1)
|
||||
|
||||
# Initialize clients
|
||||
firecrawl = FirecrawlApp(api_key=FIRECRAWL_API_KEY)
|
||||
claude = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
|
||||
|
||||
def get_user_preferences():
|
||||
"""Get apartment search preferences from user input"""
|
||||
print(f"\n{Colors.BOLD}{Colors.CYAN}=== Apartment Finder ==={Colors.RESET}")
|
||||
print(f"{Colors.CYAN}Please enter your apartment search preferences:{Colors.RESET}")
|
||||
|
||||
# Get required inputs
|
||||
location = input(f"\n{Colors.YELLOW}Enter location (city or neighborhood): {Colors.RESET}")
|
||||
while not location.strip():
|
||||
location = input(f"{Colors.RED}Location cannot be empty. Please enter a location: {Colors.RESET}")
|
||||
|
||||
budget = input(f"{Colors.YELLOW}Enter your maximum budget (e.g., $2000): {Colors.RESET}")
|
||||
while not budget.strip():
|
||||
budget = input(f"{Colors.RED}Budget cannot be empty. Please enter your maximum budget: {Colors.RESET}")
|
||||
if not budget.startswith('$'):
|
||||
budget = f"${budget}"
|
||||
|
||||
# Get optional inputs with defaults
|
||||
bedrooms = input(f"{Colors.YELLOW}Enter number of bedrooms (default: 1): {Colors.RESET}") or "1"
|
||||
|
||||
amenities = input(f"{Colors.YELLOW}Enter desired amenities, separated by commas (e.g., gym,pool,parking): {Colors.RESET}") or ""
|
||||
|
||||
return {
|
||||
"location": location.strip(),
|
||||
"budget": budget.strip(),
|
||||
"bedrooms": bedrooms.strip(),
|
||||
"amenities": amenities.strip()
|
||||
}
|
||||
|
||||
def build_search_query(user_prefs):
|
||||
amenities_str = f" with {user_prefs['amenities'].replace(',', ', ')}" if user_prefs['amenities'] else ""
|
||||
return f"{user_prefs['bedrooms']} bedroom apartments for rent in {user_prefs['location']} under {user_prefs['budget']}{amenities_str}"
|
||||
|
||||
def research_apartments(query: str) -> Dict[str, Any]:
|
||||
"""Use Firecrawl's deep research to find apartment listings"""
|
||||
print(f"\n{Colors.BOLD}{Colors.CYAN}🔍 INITIATING DEEP RESEARCH 🔍{Colors.RESET}")
|
||||
print(f"{Colors.BLUE}Researching apartments with query: '{query}'{Colors.RESET}")
|
||||
print(f"{Colors.BLUE}This may take a few minutes...{Colors.RESET}\n")
|
||||
|
||||
# Define research parameters
|
||||
params = {
|
||||
"maxDepth": 3, # Number of research iterations
|
||||
"timeLimit": 180, # Time limit in seconds
|
||||
"maxUrls": 20 # Maximum URLs to analyze
|
||||
}
|
||||
|
||||
# Start research with real-time updates
|
||||
def on_activity(activity):
|
||||
activity_type = activity['type']
|
||||
message = activity['message']
|
||||
|
||||
if activity_type == 'info':
|
||||
color = Colors.CYAN
|
||||
elif activity_type == 'search':
|
||||
color = Colors.BLUE
|
||||
elif activity_type == 'scrape':
|
||||
color = Colors.MAGENTA
|
||||
elif activity_type == 'analyze':
|
||||
color = Colors.GREEN
|
||||
else:
|
||||
color = Colors.RESET
|
||||
|
||||
print(f"[{color}{activity_type}{Colors.RESET}] {message}")
|
||||
|
||||
# Run deep research
|
||||
results = firecrawl.deep_research(
|
||||
query=query,
|
||||
params=params,
|
||||
on_activity=on_activity
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
def analyze_with_claude(research_results: Dict[str, Any], user_prefs: Dict[str, str]) -> List[Dict[str, Any]]:
|
||||
"""Use Claude to analyze apartment data and extract top options"""
|
||||
print(f"\n{Colors.BOLD}{Colors.MAGENTA}🧠 ANALYZING RESULTS WITH CLAUDE 3.7 🧠{Colors.RESET}")
|
||||
|
||||
# Extract relevant information from sources
|
||||
sources_text = "\n\n".join([
|
||||
f"Source {i+1}:\n{source.get('content', '')}"
|
||||
for i, source in enumerate(research_results['data']['sources'][:15]) # Limit to first 15 sources
|
||||
])
|
||||
|
||||
# Add the final analysis as an additional source
|
||||
final_analysis = research_results['data'].get('finalAnalysis', '')
|
||||
if final_analysis:
|
||||
sources_text += f"\n\nFinal Analysis:\n{final_analysis}"
|
||||
|
||||
# Prepare system prompt with better handling for limited data
|
||||
system_prompt = """
|
||||
You are an expert apartment finder assistant. Your task is to analyze text about apartments and find the top apartment options that best match the user's preferences.
|
||||
|
||||
If you find specific apartment listings with details, extract and organize them into exactly 3 options.
|
||||
|
||||
For each listing you can identify, extract:
|
||||
1. Price (monthly rent)
|
||||
2. Location (specific neighborhood, address if available)
|
||||
3. Key features (bedrooms, bathrooms, square footage, type of building)
|
||||
4. Amenities (both in-unit and building amenities)
|
||||
5. Pros and cons (at least 3 of each)
|
||||
|
||||
If you cannot find 3 complete listings with all details, do your best with the information available. You can:
|
||||
- Create fewer than 3 listings if that's all you can find
|
||||
- Extrapolate missing information based on similar listings or market trends
|
||||
- For missing specific details, use general information about the area
|
||||
|
||||
You MUST format your response as a JSON array of objects. Each object should have these exact fields:
|
||||
- title (string)
|
||||
- price (string)
|
||||
- location (string)
|
||||
- features (array of strings)
|
||||
- amenities (array of strings)
|
||||
- pros (array of strings)
|
||||
- cons (array of strings)
|
||||
|
||||
If you absolutely cannot find any apartment listings with enough details, return an array with a single object containing general information about apartments in the area, with "No specific listings found" as the title.
|
||||
|
||||
Example JSON structure:
|
||||
[
|
||||
{
|
||||
"title": "Luxury 2BR in Downtown",
|
||||
"price": "$2,500/month",
|
||||
"location": "123 Main St, Downtown",
|
||||
"features": ["2 bedrooms", "2 bathrooms", "950 sq ft"],
|
||||
"amenities": ["In-unit laundry", "Parking garage", "Fitness center"],
|
||||
"pros": ["Great location", "Modern appliances", "Pet friendly"],
|
||||
"cons": ["Street noise", "Small kitchen", "Limited storage"]
|
||||
}
|
||||
]
|
||||
|
||||
Return ONLY the JSON array, nothing else.
|
||||
"""
|
||||
|
||||
# Create the user message
|
||||
user_message = f"""
|
||||
I'm looking for {user_prefs['bedrooms']} bedroom apartments in {user_prefs['location']} with a budget of {user_prefs['budget']}.
|
||||
|
||||
Additional preferences: {user_prefs.get('amenities', 'None specified')}
|
||||
|
||||
Please analyze the following information and find apartment options that match my criteria:
|
||||
|
||||
{sources_text}
|
||||
"""
|
||||
|
||||
# Call Claude API
|
||||
response = claude.messages.create(
|
||||
model="claude-3-7-sonnet-20250219",
|
||||
max_tokens=4000,
|
||||
temperature=0,
|
||||
system=system_prompt,
|
||||
messages=[{"role": "user", "content": user_message}]
|
||||
)
|
||||
|
||||
# Extract and parse JSON from response with better error handling
|
||||
try:
|
||||
content = response.content[0].text
|
||||
|
||||
# Clean the content - strip markdown formatting or text before/after JSON
|
||||
content = content.strip()
|
||||
if content.startswith('```json'):
|
||||
content = content[7:]
|
||||
if content.endswith('```'):
|
||||
content = content[:-3]
|
||||
content = content.strip()
|
||||
|
||||
# Look for JSON array in the response
|
||||
if content.startswith('[') and content.endswith(']'):
|
||||
return json.loads(content)
|
||||
|
||||
# Try to find JSON brackets if not properly formatted
|
||||
json_start = content.find('[')
|
||||
json_end = content.rfind(']') + 1
|
||||
if json_start >= 0 and json_end > json_start:
|
||||
json_str = content[json_start:json_end]
|
||||
return json.loads(json_str)
|
||||
|
||||
# If we can't find JSON, create a fallback response
|
||||
print(f"{Colors.YELLOW}Could not find valid JSON in Claude's response, creating fallback response{Colors.RESET}")
|
||||
return [{
|
||||
"title": "No specific listings found",
|
||||
"price": f"Target: {user_prefs['budget']}",
|
||||
"location": user_prefs['location'],
|
||||
"features": [f"{user_prefs['bedrooms']} bedroom(s)"],
|
||||
"amenities": user_prefs['amenities'].split(',') if user_prefs['amenities'] else ["Not specified"],
|
||||
"pros": ["Information is based on general market research", "Consider visiting apartment listing websites directly", "Contact local real estate agents for current availability"],
|
||||
"cons": ["No specific listings were found in the research", "Prices and availability may vary", "Additional research recommended"]
|
||||
}]
|
||||
except Exception as e:
|
||||
print(f"{Colors.RED}Error parsing Claude's response: {e}{Colors.RESET}")
|
||||
print(f"{Colors.YELLOW}Creating fallback response{Colors.RESET}")
|
||||
return [{
|
||||
"title": "Error analyzing apartment listings",
|
||||
"price": f"Target: {user_prefs['budget']}",
|
||||
"location": user_prefs['location'],
|
||||
"features": [f"{user_prefs['bedrooms']} bedroom(s)"],
|
||||
"amenities": user_prefs['amenities'].split(',') if user_prefs['amenities'] else ["Not specified"],
|
||||
"pros": ["Try refining your search criteria", "Consider searching specific apartment websites", "Contact local real estate agents"],
|
||||
"cons": ["Search encountered technical difficulties", "Results may not be accurate", "Consider trying again later"]
|
||||
}]
|
||||
|
||||
def display_results(apartments: List[Dict[str, Any]]):
|
||||
"""Display the top apartment options in a readable format"""
|
||||
if not apartments:
|
||||
print(f"{Colors.RED}No suitable apartments found that match your criteria.{Colors.RESET}")
|
||||
return
|
||||
|
||||
print(f"\n{Colors.BOLD}{'=' * 80}{Colors.RESET}")
|
||||
print(f"{Colors.BOLD}{Colors.GREEN}🏠 TOP {len(apartments)} APARTMENT OPTIONS 🏠{Colors.RESET}".center(80))
|
||||
print(f"{Colors.BOLD}{'=' * 80}{Colors.RESET}")
|
||||
|
||||
for i, apt in enumerate(apartments):
|
||||
print(f"\n{Colors.BOLD}{Colors.CYAN}🔑 OPTION {i+1}: {apt.get('title', 'Apartment')}{Colors.RESET}")
|
||||
print(f"{Colors.YELLOW}💰 Price: {apt.get('price', 'N/A')}{Colors.RESET}")
|
||||
print(f"{Colors.YELLOW}📍 Location: {apt.get('location', 'N/A')}{Colors.RESET}")
|
||||
|
||||
print(f"\n{Colors.MAGENTA}📋 Features:{Colors.RESET}")
|
||||
for feature in apt.get('features', []):
|
||||
print(f" {Colors.BLUE}•{Colors.RESET} {feature}")
|
||||
|
||||
print(f"\n{Colors.MAGENTA}✨ Amenities:{Colors.RESET}")
|
||||
for amenity in apt.get('amenities', []):
|
||||
print(f" {Colors.BLUE}•{Colors.RESET} {amenity}")
|
||||
|
||||
print(f"\n{Colors.GREEN}👍 Pros:{Colors.RESET}")
|
||||
for pro in apt.get('pros', []):
|
||||
print(f" {Colors.BLUE}•{Colors.RESET} {pro}")
|
||||
|
||||
print(f"\n{Colors.RED}👎 Cons:{Colors.RESET}")
|
||||
for con in apt.get('cons', []):
|
||||
print(f" {Colors.BLUE}•{Colors.RESET} {con}")
|
||||
|
||||
print(f"\n{Colors.CYAN}{'-' * 80}{Colors.RESET}")
|
||||
|
||||
def main():
|
||||
# Get user preferences through interactive input
|
||||
user_prefs = get_user_preferences()
|
||||
|
||||
# Print summary of search criteria
|
||||
print(f"\n{Colors.BOLD}{Colors.CYAN}=== Search Criteria ==={Colors.RESET}")
|
||||
print(f"{Colors.BLUE}Location: {Colors.YELLOW}{user_prefs['location']}{Colors.RESET}")
|
||||
print(f"{Colors.BLUE}Budget: {Colors.YELLOW}{user_prefs['budget']}{Colors.RESET}")
|
||||
print(f"{Colors.BLUE}Bedrooms: {Colors.YELLOW}{user_prefs['bedrooms']}{Colors.RESET}")
|
||||
print(f"{Colors.BLUE}Amenities: {Colors.YELLOW}{user_prefs['amenities'] or 'None specified'}{Colors.RESET}")
|
||||
|
||||
# Build search query
|
||||
query = build_search_query(user_prefs)
|
||||
|
||||
# Run research
|
||||
research_results = research_apartments(query)
|
||||
|
||||
# Analyze with Claude
|
||||
top_apartments = analyze_with_claude(research_results, user_prefs)
|
||||
|
||||
# Display results
|
||||
display_results(top_apartments)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
3
examples/deep-research-apartment-finder/requirements.txt
Normal file
3
examples/deep-research-apartment-finder/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
anthropic==0.18.0
|
||||
firecrawl==0.2.0
|
||||
python-dotenv==1.0.0
|
Loading…
x
Reference in New Issue
Block a user