Add AI Property Assistant contribution only
This commit is contained in:
162
week1/community-contributions/AI_Property_Assistant/README.md
Normal file
162
week1/community-contributions/AI_Property_Assistant/README.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# AI Property Rental Assistant
|
||||||
|
|
||||||
|
A Python tool that scrapes UK property rental listings and uses OpenAI's GPT-4o-mini to provide personalized property recommendations based on your requirements.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
- Scrapes property listings from OnTheMarket.com
|
||||||
|
- Uses AI to analyze properties against your specific needs
|
||||||
|
- Provides smart recommendations with reasons why properties match
|
||||||
|
- Works for any UK location (currently configured for Durham)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- Python 3.7+
|
||||||
|
- OpenAI API key ([Get one here](https://platform.openai.com/api-keys))
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. **Install required packages:**
|
||||||
|
```bash
|
||||||
|
pip install requests beautifulsoup4 openai python-dotenv ipython
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Set up your API key:**
|
||||||
|
|
||||||
|
Create a `.env` file in the same directory as your script:
|
||||||
|
```
|
||||||
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run the script:**
|
||||||
|
```bash
|
||||||
|
python your_script_name.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Use
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
The script is pre-configured to search for student housing in Durham. Just run it and you'll get AI-powered recommendations!
|
||||||
|
|
||||||
|
### Customizing Your Search
|
||||||
|
|
||||||
|
**Change the location:**
|
||||||
|
```python
|
||||||
|
website_url = "https://www.onthemarket.com/to-rent/property/manchester/"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update your requirements:**
|
||||||
|
```python
|
||||||
|
user_needs = "I'm a young professional looking for a 1-bedroom flat in Manchester under £1,000/month"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Requirements You Can Use:
|
||||||
|
- `"Student looking for shared accommodation under £600/month"`
|
||||||
|
- `"Family needing 3-bedroom house with garden under £1,500/month"`
|
||||||
|
- `"Professional couple wanting modern 2-bed apartment near city center"`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Supported Cities
|
||||||
|
Replace `durham` in the URL with any UK city:
|
||||||
|
- `london` - London properties
|
||||||
|
- `manchester` - Manchester properties
|
||||||
|
- `birmingham` - Birmingham properties
|
||||||
|
- `leeds` - Leeds properties
|
||||||
|
- `bristol` - Bristol properties
|
||||||
|
|
||||||
|
### AI Behavior
|
||||||
|
The system prompt is configured for UK rentals but you can modify it in the `system_prompt` variable to:
|
||||||
|
- Focus on specific property types
|
||||||
|
- Emphasize certain features (parking, garden, etc.)
|
||||||
|
- Target different tenant types (students, families, professionals)
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Website Title: Properties to rent in Durham - OnTheMarket
|
||||||
|
Content Length: 15847 characters
|
||||||
|
|
||||||
|
==================================================
|
||||||
|
RENTAL RECOMMENDATIONS:
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
# Property Recommendations for Durham
|
||||||
|
|
||||||
|
Based on your requirements for a 2-bedroom student property under £2,000/month, here are my top recommendations:
|
||||||
|
|
||||||
|
## 1. **Student House on North Road** - £1,600/month
|
||||||
|
- **Bedrooms:** 2
|
||||||
|
- **Perfect because:** Well within budget, popular student area
|
||||||
|
- **Features:** Close to university, furnished, bills included
|
||||||
|
|
||||||
|
## 2. **Modern Apartment City Centre** - £1,400/month
|
||||||
|
- **Bedrooms:** 2
|
||||||
|
- **Perfect because:** Great location, modern amenities
|
||||||
|
- **Features:** Parking space, balcony, near shops
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Create a `requirements.txt` file:
|
||||||
|
```
|
||||||
|
requests>=2.28.0
|
||||||
|
beautifulsoup4>=4.11.0
|
||||||
|
openai>=1.0.0
|
||||||
|
python-dotenv>=0.19.0
|
||||||
|
ipython>=8.0.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Install with: `pip install -r requirements.txt`
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
### API Costs
|
||||||
|
- Uses GPT-4o-mini model (very affordable - ~$0.001 per request)
|
||||||
|
- Monitor usage at: https://platform.openai.com/usage
|
||||||
|
|
||||||
|
### Rate Limits
|
||||||
|
- Free OpenAI accounts: 3 requests per minute
|
||||||
|
- The script makes 1 request per run
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Web Scraping:** Downloads the property listing page
|
||||||
|
2. **Text Extraction:** Cleans HTML and extracts property information
|
||||||
|
3. **AI Analysis:** Sends your requirements + property data to GPT-4
|
||||||
|
4. **Smart Recommendations:** AI filters and ranks properties with explanations
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
**"No API key found"**
|
||||||
|
- Make sure `.env` file exists in the same folder as your script
|
||||||
|
- Check the API key has no extra spaces
|
||||||
|
- Verify it starts with `sk-proj-`
|
||||||
|
|
||||||
|
**"Error fetching website"**
|
||||||
|
- Check your internet connection
|
||||||
|
- Try a different city URL
|
||||||
|
- Some websites may temporarily block requests
|
||||||
|
|
||||||
|
**No good recommendations**
|
||||||
|
- Try adjusting your budget or requirements
|
||||||
|
- Check if the website loaded properly (look at content length)
|
||||||
|
- Try a different city with more properties
|
||||||
|
|
||||||
|
## Possible Improvements
|
||||||
|
|
||||||
|
- Make it interactive (ask for user input)
|
||||||
|
- Support multiple property websites
|
||||||
|
- Add price tracking over time
|
||||||
|
- Include property images in analysis
|
||||||
|
- Create a simple web interface
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This tool is for educational purposes. Always verify property information directly with landlords or estate agents. Respect website terms of service.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need help?** Check that your `.env` file is set up correctly and you have an active internet connection. The script will tell you if there are any issues with your API key!
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "0993b44c-58f3-4d7d-ac31-80871a867040",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# AI Property Rental Assistant for Durham\n",
|
||||||
|
"This notebook creates an intelligent property rental assistant that:\n",
|
||||||
|
"1. Scrapes rental property listings from OnTheMarket.com\n",
|
||||||
|
"2. Uses OpenAI's GPT-4o-mini to analyze and recommend properties based on user preferences\n",
|
||||||
|
"3. Provides formatted recommendations in markdown for easy reading\n",
|
||||||
|
"\n",
|
||||||
|
"Purpose: Help students and professionals find suitable rental properties in Durham, UK"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "6f3fa597-bac5-496f-b0c6-ac1cb524062d",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"import os\n",
|
||||||
|
"import requests\n",
|
||||||
|
"from dotenv import load_dotenv\n",
|
||||||
|
"from bs4 import BeautifulSoup\n",
|
||||||
|
"from IPython.display import Markdown, display\n",
|
||||||
|
"from openai import OpenAI"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "dfa715c4-81d4-4f1e-87d8-6cf7fa17db71",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# =====================================\n",
|
||||||
|
"# STEP 1: ENVIRONMENT SETUP & API KEYS\n",
|
||||||
|
"# =====================================\n",
|
||||||
|
"\n",
|
||||||
|
"# Load environment variables from .env file\n",
|
||||||
|
"# Make sure you have a .env file with: OPENAI_API_KEY=your_key_here\n",
|
||||||
|
"load_dotenv(override=True)\n",
|
||||||
|
"api_key = os.getenv('OPENAI_API_KEY')\n",
|
||||||
|
"\n",
|
||||||
|
"# Validate the OpenAI API key format and existence\n",
|
||||||
|
"if not api_key:\n",
|
||||||
|
" print(\"No API key was found - please head over to the troubleshooting notebook in this folder to identify & fix!\")\n",
|
||||||
|
"elif not api_key.startswith(\"sk-proj-\"):\n",
|
||||||
|
" print(\"An API key was found, but it doesn't start sk-proj-; please check you're using the right key - see troubleshooting notebook\")\n",
|
||||||
|
"elif api_key.strip() != api_key:\n",
|
||||||
|
" print(\"An API key was found, but it looks like it might have space or tab characters at the start or end - please remove them - see troubleshooting notebook\")\n",
|
||||||
|
"else:\n",
|
||||||
|
" print(\"API key found and looks good so far!\")\n",
|
||||||
|
"\n",
|
||||||
|
"# Initialize OpenAI client\n",
|
||||||
|
"openai = OpenAI()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "c7e44572-1cda-42d2-a6ff-45f462fd436f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# =====================================\n",
|
||||||
|
"# STEP 2: WEB SCRAPING SETUP\n",
|
||||||
|
"# =====================================\n",
|
||||||
|
"\n",
|
||||||
|
"# HTTP headers to mimic a real browser request\n",
|
||||||
|
"# Many websites block requests without proper headers\n",
|
||||||
|
"headers = {\n",
|
||||||
|
" \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\"\n",
|
||||||
|
"}\n",
|
||||||
|
"\n",
|
||||||
|
"class Website:\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" A class to represent and scrape content from a webpage.\n",
|
||||||
|
" \n",
|
||||||
|
" This class handles:\n",
|
||||||
|
" - Fetching HTML content from a URL\n",
|
||||||
|
" - Parsing HTML with BeautifulSoup\n",
|
||||||
|
" - Extracting clean text content (removing scripts, styles, etc.)\n",
|
||||||
|
" - Error handling for failed requests\n",
|
||||||
|
" \n",
|
||||||
|
" Attributes:\n",
|
||||||
|
" url (str): The URL of the website\n",
|
||||||
|
" title (str): The page title\n",
|
||||||
|
" text (str): Clean text content from the page body\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" \n",
|
||||||
|
" def __init__(self, url):\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" Initialize Website object by scraping content from the given URL.\n",
|
||||||
|
" \n",
|
||||||
|
" Args:\n",
|
||||||
|
" url (str): The website URL to scrape\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" self.url = url\n",
|
||||||
|
" try:\n",
|
||||||
|
" # Make HTTP request with timeout to prevent hanging\n",
|
||||||
|
" response = requests.get(url, headers=headers, timeout=10)\n",
|
||||||
|
" response.raise_for_status() # Raises an HTTPError for bad responses\n",
|
||||||
|
" \n",
|
||||||
|
" # Parse HTML content\n",
|
||||||
|
" soup = BeautifulSoup(response.content, 'html.parser')\n",
|
||||||
|
" \n",
|
||||||
|
" # Extract page title\n",
|
||||||
|
" self.title = soup.title.string if soup.title else \"No title found\"\n",
|
||||||
|
" \n",
|
||||||
|
" # Clean up the HTML by removing irrelevant elements\n",
|
||||||
|
" if soup.body:\n",
|
||||||
|
" # Remove scripts, styles, images, and input elements\n",
|
||||||
|
" for irrelevant in soup.body([\"script\", \"style\", \"img\", \"input\"]):\n",
|
||||||
|
" irrelevant.decompose()\n",
|
||||||
|
" \n",
|
||||||
|
" # Extract clean text with proper line separation\n",
|
||||||
|
" self.text = soup.body.get_text(separator=\"\\n\", strip=True)\n",
|
||||||
|
" else:\n",
|
||||||
|
" self.text = \"No body content found\"\n",
|
||||||
|
" \n",
|
||||||
|
" except requests.RequestException as e:\n",
|
||||||
|
" # Handle network errors gracefully\n",
|
||||||
|
" print(f\"Error fetching website: {e}\")\n",
|
||||||
|
" self.title = \"Error loading page\"\n",
|
||||||
|
" self.text = \"Could not load page content\""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "a97d9c34-2831-4730-949e-bba1b6ac9bb3",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# =====================================\n",
|
||||||
|
"# STEP 3: AI ASSISTANT FUNCTIONS\n",
|
||||||
|
"# =====================================\n",
|
||||||
|
"\n",
|
||||||
|
"def house_renting(system_prompt, user_prompt):\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" Send prompts to OpenAI's GPT model and get rental recommendations.\n",
|
||||||
|
" \n",
|
||||||
|
" This function:\n",
|
||||||
|
" - Formats the conversation for the AI model\n",
|
||||||
|
" - Sends requests to GPT-4o-mini (cost-effective model)\n",
|
||||||
|
" - Returns the AI's response as a string\n",
|
||||||
|
" \n",
|
||||||
|
" Args:\n",
|
||||||
|
" system_prompt (str): Instructions for how the AI should behave\n",
|
||||||
|
" user_prompt (str): The user's specific request with property data\n",
|
||||||
|
" \n",
|
||||||
|
" Returns:\n",
|
||||||
|
" str: AI-generated rental recommendations in markdown format\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" messages = [\n",
|
||||||
|
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
||||||
|
" {\"role\": \"user\", \"content\": user_prompt}\n",
|
||||||
|
" ]\n",
|
||||||
|
" \n",
|
||||||
|
" # Call OpenAI API\n",
|
||||||
|
" response = openai.chat.completions.create(\n",
|
||||||
|
" model=\"gpt-4o-mini\", # Cost-effective model, good for this task\n",
|
||||||
|
" messages=messages,\n",
|
||||||
|
" )\n",
|
||||||
|
" \n",
|
||||||
|
" return response.choices[0].message.content"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "6d0c4b96-b907-45ed-8a4d-a67d8f7e4f33",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# =====================================\n",
|
||||||
|
"# STEP 4: AI SYSTEM CONFIGURATION\n",
|
||||||
|
"# =====================================\n",
|
||||||
|
"\n",
|
||||||
|
"# Define how the AI assistant should behave\n",
|
||||||
|
"# This is crucial for getting consistent, helpful responses\n",
|
||||||
|
"system_prompt = \"\"\"\n",
|
||||||
|
"You are a helpful real estate assistant specializing in UK property rentals. Your job is to guide users in finding houses to rent, especially in Durham. Follow these rules:\n",
|
||||||
|
"\n",
|
||||||
|
"1. Always ask clarifying questions if user input is vague. Determine location, budget, number of bedrooms, and tenant type (e.g. student, family, professional).\n",
|
||||||
|
"2. Use structured data provided from the website (like property listings) to identify relevant options.\n",
|
||||||
|
"3. If listings are provided, filter and rank them based on the user's preferences.\n",
|
||||||
|
"4. Recommend up to 5 top properties with rent price, bedroom count, key features, and location.\n",
|
||||||
|
"5. Always respond in markdown with clean formatting using headers, bold text, and bullet points.\n",
|
||||||
|
"6. If no listings match well, provide tips (e.g. \"try adjusting your budget or search radius\").\n",
|
||||||
|
"7. Stay concise, helpful, and adapt to whether the user is a student, family, couple, or solo tenant.\n",
|
||||||
|
"\"\"\"\n",
|
||||||
|
"\n",
|
||||||
|
"def user_prompt_for_renting(website, user_needs):\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" Create a formatted prompt that combines user requirements with scraped property data.\n",
|
||||||
|
" \n",
|
||||||
|
" This function:\n",
|
||||||
|
" - Takes user preferences and website content\n",
|
||||||
|
" - Formats them into a clear prompt for the AI\n",
|
||||||
|
" - Limits content to first 4000 characters to stay within token limits\n",
|
||||||
|
" \n",
|
||||||
|
" Args:\n",
|
||||||
|
" website (Website): The scraped website object\n",
|
||||||
|
" user_needs (str): Description of what the user is looking for\n",
|
||||||
|
" \n",
|
||||||
|
" Returns:\n",
|
||||||
|
" str: Formatted prompt ready to send to the AI\n",
|
||||||
|
" \"\"\"\n",
|
||||||
|
" user_prompt = f\"\"\"\n",
|
||||||
|
"I want to rent a house and here's what I'm looking for:\n",
|
||||||
|
"{user_needs}\n",
|
||||||
|
"\n",
|
||||||
|
"Here are the property listings I found on the website titled: \"{website.title}\".\n",
|
||||||
|
"Please analyze them and recommend the best 3–5 options that match my needs. If none are suitable, tell me why and offer suggestions.\n",
|
||||||
|
"\n",
|
||||||
|
"The page content is below:\n",
|
||||||
|
"{website.text[:4000]} # Truncated to first 4000 characters to manage token usage\n",
|
||||||
|
"\"\"\"\n",
|
||||||
|
" return user_prompt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "cecb1f11-060a-4737-828c-e94ae04a42ae",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# =====================================\n",
|
||||||
|
"# STEP 5: MAIN EXECUTION\n",
|
||||||
|
"# =====================================\n",
|
||||||
|
"\n",
|
||||||
|
"if __name__ == \"__main__\":\n",
|
||||||
|
" print(\"Starting AI Property Rental Assistant...\")\n",
|
||||||
|
" print(\"=\" * 50)\n",
|
||||||
|
" \n",
|
||||||
|
" # Configure the property search\n",
|
||||||
|
" website_url = \"https://www.onthemarket.com/to-rent/property/durham/\"\n",
|
||||||
|
" print(f\"🔍 Scraping properties from: {website_url}\")\n",
|
||||||
|
" \n",
|
||||||
|
" # Scrape the website\n",
|
||||||
|
" website = Website(website_url)\n",
|
||||||
|
" \n",
|
||||||
|
" # Display scraping results\n",
|
||||||
|
" print(f\"Website Title: {website.title}\")\n",
|
||||||
|
" print(f\"Content Length: {len(website.text)} characters\")\n",
|
||||||
|
" print(f\"Successfully scraped property listings\")\n",
|
||||||
|
" print()\n",
|
||||||
|
" \n",
|
||||||
|
" # Define user requirements\n",
|
||||||
|
" # TODO: Make this interactive by adding input() statements\n",
|
||||||
|
" user_needs = \"I'm a student looking for a 2-bedroom house in Durham under £2,000/month\"\n",
|
||||||
|
" print(f\"User Requirements: {user_needs}\")\n",
|
||||||
|
" print()\n",
|
||||||
|
" \n",
|
||||||
|
" # Generate AI prompt\n",
|
||||||
|
" user_prompt = user_prompt_for_renting(website, user_needs)\n",
|
||||||
|
" \n",
|
||||||
|
" # Get AI recommendations\n",
|
||||||
|
" print(\"Generating AI recommendations...\")\n",
|
||||||
|
" output = house_renting(system_prompt, user_prompt)\n",
|
||||||
|
" \n",
|
||||||
|
" # Display results\n",
|
||||||
|
" display(Markdown(output))\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python [conda env:llms]",
|
||||||
|
"language": "python",
|
||||||
|
"name": "conda-env-llms-py"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.11.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user