<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Otto Brennan</title>
    <description>The latest articles on Forem by Otto Brennan (@ottobrennan).</description>
    <link>https://forem.com/ottobrennan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3807115%2Ffb015917-47f3-4e63-8c9a-6dd4e47490cb.png</url>
      <title>Forem: Otto Brennan</title>
      <link>https://forem.com/ottobrennan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ottobrennan"/>
    <language>en</language>
    <item>
      <title>ChatGPT for Photographers: The 5 Prompts That Actually Save Time</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 14:14:52 +0000</pubDate>
      <link>https://forem.com/ottobrennan/chatgpt-for-photographers-the-5-prompts-that-actually-save-time-4b9k</link>
      <guid>https://forem.com/ottobrennan/chatgpt-for-photographers-the-5-prompts-that-actually-save-time-4b9k</guid>
      <description>&lt;p&gt;Photography is a creative business. But running a photography business? That's a lot of writing, quoting, scheduling, and client communication — none of which you went to school for.&lt;/p&gt;

&lt;p&gt;I've been talking to photographers about where their non-shooting time goes. The answer is always the same: emails, contracts, social captions, and client questionnaires. Hours every week.&lt;/p&gt;

&lt;p&gt;ChatGPT doesn't make you a better photographer. But it makes you a faster business owner. Here's what's working.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Inquiry Response Email
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Someone fills out your contact form. Now you need to write a response that's warm, professional, explains your pricing, and gets them to book a call — without sounding like a form letter.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm a photographer responding to a new inquiry.
Service inquired about: [wedding / portrait / commercial / etc.]
What they said: [paste their message]

Write a response email that:
- Feels personal and warm, not templated
- Briefly explains my process
- Mentions my starting price of $[X]
- Asks to schedule a discovery call
- Keeps it under 150 words
My name: [name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get 80% of the way there in 10 seconds. Tweak the personal details and send.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Instagram Caption Batch
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You have 10 great photos ready to post. You have zero captions. Writing them one at a time is painful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm a [wedding / portrait / commercial] photographer. Write 5 Instagram captions for photos from a recent [session type].

My style/tone: [warm and personal / professional / funny and casual]
The session details: [brief description — golden hour engagement shoot in the mountains, etc.]

Each caption should:
- Be 3-5 sentences
- Include a call to action (book, DM, etc.)
- End with 3-5 relevant hashtags
Don't make them sound AI-generated. Keep them conversational.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batch 5 at once. Edit lightly. Schedule them out. Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Pricing Explanation Email
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Client asks "why does this cost so much?" You know the answer. But explaining your value under pressure is hard, and defensive emails never land well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm a photographer and a potential client has asked why my prices are higher than others they've seen.
My starting price: $[X]
What's included: [list]
Years of experience: [X]

Write a confident, non-defensive email that:
- Acknowledges their question respectfully
- Explains what they're actually getting
- Positions quality and experience without badmouthing competitors
- Ends with an invitation to talk more
Tone: professional, warm, confident.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the hardest emails to write on your own. Let ChatGPT do the first draft.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Client Questionnaire
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You need to understand the vibe, must-have shots, family dynamics, timeline, and logistics for a shoot. But building a questionnaire from scratch takes time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a pre-shoot questionnaire for a [wedding / newborn / family / corporate headshot] photography client.
Include questions about:
- The story behind the session
- Must-have shots
- People/subjects involved
- Location preferences
- Style and mood preferences
- Any concerns or sensitivities
- Logistics (timing, access, parking)

Format as a simple form they can fill out. Keep questions friendly and easy to answer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Customize for your style, then use it for every client. One-time work, recurring value.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The "About Me" Page Rewrite
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Your about page has been the same for three years and you wrote it at midnight when you were exhausted. It sounds nothing like how you actually talk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Rewrite my photography website's About page.
Here's my current version: [paste it]

I want it to:
- Sound like I'm actually talking to someone, not writing a formal bio
- Mention what I specialize in: [specialty]
- Explain why I became a photographer: [your story, briefly]
- Tell people what working with me is like
- End with a call to action to book or reach out
Length: 200-250 words. First person. Warm and genuine.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll probably keep 60% of what it writes and rewrite the rest. That's still 5x faster than starting from zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  The time math
&lt;/h2&gt;

&lt;p&gt;If these 5 prompts save you 20 minutes each per week, that's 100 minutes back — almost two hours. Over a month, that's an extra half-day you didn't have before.&lt;/p&gt;

&lt;p&gt;For most photographers, the bottleneck isn't the photography. It's everything around it. These prompts won't run your business, but they'll make the admin side a lot less exhausting.&lt;/p&gt;

&lt;p&gt;Pick one. Try it today.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Want 25 ready-made prompts for your small business — covering emails, social, client communication, and more? Check out the &lt;a href="https://ottobrennan.gumroad.com/l/lndaa" rel="noopener noreferrer"&gt;Small Business AI Starter Kit&lt;/a&gt; ($9 one-time).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>photography</category>
      <category>smallbusiness</category>
      <category>entrepreneur</category>
    </item>
    <item>
      <title>How Accountants Are Using ChatGPT to Cut Client Work in Half</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 14:14:18 +0000</pubDate>
      <link>https://forem.com/ottobrennan/how-accountants-are-using-chatgpt-to-cut-client-work-in-half-2c4c</link>
      <guid>https://forem.com/ottobrennan/how-accountants-are-using-chatgpt-to-cut-client-work-in-half-2c4c</guid>
      <description>&lt;p&gt;I work with a lot of small business owners. One of the most common things I hear from their accountants: "I spend more time explaining things than actually doing accounting."&lt;/p&gt;

&lt;p&gt;Client emails. Explaining statements. Writing up what a report means. Chasing down missing info. It's constant.&lt;/p&gt;

&lt;p&gt;ChatGPT doesn't do your accounting for you. But it handles a surprising amount of the surrounding work — the writing, the explaining, the communicating. Here's what's actually working for accountants and bookkeepers right now.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Client Email Explainer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You sent a financial statement. Client doesn't understand it. Now you're writing a three-paragraph email explaining it from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm a bookkeeper/accountant writing to a small business client.
Here is the financial data I need to explain:
[paste the data or key numbers]

Write a plain-English email that:
- Explains what these numbers mean for their business
- Highlights any areas to pay attention to
- Uses no accounting jargon
- Has a friendly, professional tone
Keep it under 200 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clients actually read emails like this. And you wrote it in 30 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Missing Document Chaser
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You need a bank statement from March. You've asked three times. You're still waiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a polite but firm follow-up email requesting missing financial documents from a small business client.
I need: [list what you need]
I've already asked: [number] times
Deadline is: [date]
Tone: professional but urgent. Make it easy for them to respond.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is the last line — "make it easy to respond." ChatGPT will often include a simple checklist or reply format that actually gets action.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Tax Prep Checklist Generator
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Every client is different. Writing a custom checklist each time is tedious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a tax preparation checklist for a small business owner who:
- Business type: [LLC / sole proprietor / S-corp / etc.]
- Industry: [retail / service / construction / etc.]
- Employees: [yes/no, how many]
- Uses [QuickBooks / Wave / spreadsheets / etc.]

Format as a simple checklist they can tick off. Include only what's relevant to their situation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This takes about 45 seconds and saves you from sending a generic 40-item checklist that confuses everyone.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The "What Does This Mean" Translator
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Client looks at their P&amp;amp;L and says "what does this mean for me?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm explaining a profit and loss statement to a [restaurant owner / contractor / retail shop owner].
Here are the key numbers:
- Revenue: $[X]
- Cost of goods sold: $[X]
- Gross profit: $[X]
- Operating expenses: $[X]
- Net income: $[X]

Explain in simple terms:
1. What's going well
2. What to watch out for
3. One action they could take to improve profitability

Write it like you're talking to someone with no financial background.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns a number-heavy conversation into a productive one. Clients leave the meeting actually understanding something.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The Onboarding Welcome Email
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; New client. You need to explain your process, set expectations, and ask for initial documents. Writing this from scratch every time is a waste.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write an onboarding email for a new accounting/bookkeeping client.
Include:
- Warm welcome
- Overview of the process for the first 30 days
- List of documents I'll need from them to get started
- How to send documents securely: [your method]
- My contact info and best way to reach me
- Friendly, professional tone

Documents I need: [list them]
My name: [name]
Business name: [name]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write this once, refine it, save it as a template. Takes 2 minutes to customize per client.&lt;/p&gt;




&lt;h2&gt;
  
  
  What ChatGPT won't do
&lt;/h2&gt;

&lt;p&gt;Just to be clear: it won't file returns, won't catch errors in your data, and won't replace professional judgment. It's a writing and communication tool, not an accounting tool.&lt;/p&gt;

&lt;p&gt;But if you're spending hours per week on client communication, follow-ups, and explanations — that's recoverable time. And for most accountants I talk to, that's a significant chunk of the day.&lt;/p&gt;

&lt;p&gt;Start with the client explainer email. If that saves you 20 minutes tomorrow, you'll have your answer about whether this is worth continuing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Running a small business and want ready-made prompts like these? I put together a &lt;a href="https://ottobrennan.gumroad.com/l/lndaa" rel="noopener noreferrer"&gt;25-prompt pack specifically for small business owners&lt;/a&gt; — covers social media, emails, client communication, and more.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>accounting</category>
      <category>smallbusiness</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How Hair Salon Owners Are Using ChatGPT to Fill Their Books Faster</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 13:36:37 +0000</pubDate>
      <link>https://forem.com/ottobrennan/how-hair-salon-owners-are-using-chatgpt-to-fill-their-books-faster-1io2</link>
      <guid>https://forem.com/ottobrennan/how-hair-salon-owners-are-using-chatgpt-to-fill-their-books-faster-1io2</guid>
      <description>&lt;p&gt;If you own a salon or you're a stylist running your own book, you know that the work doesn't stop when you put down the scissors. You've got Instagram to feed, Google reviews to respond to, appointment reminders to send, and a referral program nobody's heard about because you never had time to write the post.&lt;/p&gt;

&lt;p&gt;This is where ChatGPT becomes genuinely useful — not for doing your job, but for handling the communication tasks that pile up around it.&lt;/p&gt;

&lt;p&gt;Here are six ways salon owners are using it right now to stay booked.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Instagram Captions for Before-and-After Photos
&lt;/h2&gt;

&lt;p&gt;Before-and-afters are the most powerful content you can post. The photos do the work — but you still need a caption that gets people to stop scrolling and actually book.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write an Instagram caption for a hair salon posting a before-and-after photo. The client went from dark brown to a warm caramel balayage. The service took 3 hours. The caption should highlight the transformation, mention that the client cried (happy tears) when she saw it, and end with a call to book a consultation. Keep it under 120 words. Friendly, warm tone — not salesy."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Tweak the details to match your actual service and client. You'll have a ready-to-post caption in about 20 seconds. Do this after every transformation and your feed builds itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Responding to Google Reviews
&lt;/h2&gt;

&lt;p&gt;You know you should respond to reviews — good and bad. But sitting down to write a response when you're exhausted after a full day of standing is genuinely hard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt for a positive review:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a response to a 5-star Google review for a hair salon. The client said: 'Maria is absolutely amazing. She really listened to what I wanted and the color came out even better than I imagined. Will definitely be back!' The response should thank them by name, mention the service (color), and invite them to book again. Under 60 words. Sound like a real human, not a corporate script."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt for a negative review:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a professional, empathetic response to a 3-star Google review for a hair salon. The client said they loved the cut but thought the price was higher than expected. The response should acknowledge their feedback, apologize for the surprise, explain that pricing is discussed at consultation, and invite them to call to discuss. Calm, non-defensive tone. Under 80 words."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Responding to reviews improves your local SEO and shows potential new clients that you're attentive. ChatGPT makes it fast enough that you'll actually do it.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Appointment Reminder Messages
&lt;/h2&gt;

&lt;p&gt;No-shows cost you real money. A well-timed reminder dramatically reduces them — but writing a fresh one every time is tedious.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a friendly appointment reminder text message for a hair salon. The appointment is tomorrow at 2pm with stylist Jenna. The message should remind them of the time and stylist, mention the 24-hour cancellation policy, and include a quick note to reply CONFIRM or CANCEL. Keep it under 60 words. Warm and professional, not robotic."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Save a few versions of this — one for cuts, one for color, one for treatments. Use them as templates in whatever booking software you use. If your software sends automatic reminders, this prompt helps you write the template once and forget it.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Referral Program Announcements
&lt;/h2&gt;

&lt;p&gt;Word of mouth is your best marketing — but most salons never formalize it into an actual referral program because they never wrote the announcement. ChatGPT can do it in a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a social media post announcing a referral program for a hair salon. The offer is: refer a friend and you both get $20 off your next service. The post should explain how it works clearly, make it feel like a thank-you to loyal clients rather than a sales pitch, and end with a simple CTA (like 'DM us your referral's name'). Under 150 words. Warm and genuine tone."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Post this, pin it to your profile, and put a version of it in your next client reminder email. A referral program that nobody knows about doesn't work. This is the step most salons skip.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. New Stylist Introduction Posts
&lt;/h2&gt;

&lt;p&gt;Adding someone to your team? You need to introduce them in a way that makes their first few weeks busy, not slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write an Instagram post introducing a new stylist joining a hair salon team. Her name is Sofia. She specializes in lived-in color and curly hair. She trained at [Academy/School] and has 5 years of experience. The post should make her feel welcomed, highlight her specialties, mention that her books are now open, and end with a CTA to book with her. 100-130 words. Warm, community-feel tone."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fill in the real details. Add a photo. This is the kind of post that gets shared — which is exactly what a new stylist needs in her first month.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Price Increase Announcements
&lt;/h2&gt;

&lt;p&gt;This is the one everyone dreads. A price increase is necessary and fair — but communicating it badly can cause friction with loyal clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a message announcing a price increase for a hair salon. Prices are increasing by 10% starting [date]. The message should: thank clients for their loyalty, acknowledge that costs have risen (without being specific), be direct about what's changing and when, and reassure them that the quality of service is not changing. Tone should be confident and warm — not apologetic or defensive. 100-130 words. Suitable for email or Instagram caption."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The key is to be direct without over-explaining. Clients respect confidence. Apologizing excessively or burying the news makes it worse. ChatGPT will give you a solid draft — just read it over and make sure it sounds like you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern Behind All of This
&lt;/h2&gt;

&lt;p&gt;Every one of these tasks has something in common: you already know what you want to say. You just don't want to stare at a blank screen to figure out how to say it.&lt;/p&gt;

&lt;p&gt;ChatGPT solves the blank screen problem. It gives you a draft in seconds, you edit it to sound like yourself, and you're done. That's it.&lt;/p&gt;

&lt;p&gt;For a salon owner who's already managing appointments, managing a team, managing inventory, and managing clients — that's not a small thing. That's an hour or two a day back in your pocket.&lt;/p&gt;

&lt;p&gt;Pick one of the six above. Try it today.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Otto Brennan is an American expat based in Lisbon who writes about AI tools for small business owners. He's been watching people like you use these tools for the past year and reporting on what actually works.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>smallbusiness</category>
      <category>marketing</category>
      <category>ai</category>
    </item>
    <item>
      <title>ChatGPT for Personal Trainers: The 6 Prompts I Give Every New Client</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 13:30:30 +0000</pubDate>
      <link>https://forem.com/ottobrennan/chatgpt-for-personal-trainers-the-6-prompts-i-give-every-new-client-3kjp</link>
      <guid>https://forem.com/ottobrennan/chatgpt-for-personal-trainers-the-6-prompts-i-give-every-new-client-3kjp</guid>
      <description>&lt;p&gt;Running a personal training business means you're constantly creating content nobody teaches you how to make in your certification course. Workout descriptions. Nutrition emails. Instagram captions. Check-in messages. Client reminders.&lt;/p&gt;

&lt;p&gt;It never ends — and most of it eats time you could be spending on actual training.&lt;/p&gt;

&lt;p&gt;I've been watching fitness coaches figure out ChatGPT over the past year, and the ones who get it right aren't using it to replace their expertise. They're using it to handle the writing tasks that slow them down. Here are the six prompts that come up over and over again.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Personalized Workout Plan Descriptions
&lt;/h2&gt;

&lt;p&gt;You build the program. ChatGPT explains it in plain English so your client actually understands what they're doing and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm a personal trainer writing a workout description for a client. The workout is a 3-day-per-week full body strength program for a 45-year-old woman who wants to lose weight and build confidence in the gym. She's a beginner. Write a 150-word overview of the program that explains what she'll be doing, why it works, and what results she can expect in 8 weeks. Tone should be encouraging and easy to understand."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Paste this into the client's welcome packet or send it as an intro email. Clients who understand their program stick to it. This prompt alone can improve retention.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Weekly Nutrition Tip Emails
&lt;/h2&gt;

&lt;p&gt;Every trainer knows clients want nutrition guidance. Most trainers don't have time to write a fresh email every week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a short, practical nutrition tip email for personal training clients who are trying to lose body fat. This week's topic: how to handle eating out at restaurants without blowing their progress. Keep it under 200 words, conversational tone, no diet-culture language. End with one simple action they can take this week."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Batch these out. Spend 30 minutes one afternoon and generate 8 weeks of nutrition tip emails. Schedule them in advance and you've got consistent client communication on autopilot.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Client Progress Check-In Messages
&lt;/h2&gt;

&lt;p&gt;Check-ins are one of the highest-ROI things you can do for retention — but they're awkward to write and easy to skip when you're busy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a check-in message to send to a personal training client who just completed their 4th week of training. Her name is [Name]. She's been consistent with attendance but mentioned last week that she's struggling with her energy levels. The message should acknowledge her consistency, ask how the energy issue is going, and remind her that progress isn't always visible at 4 weeks. Warm, direct tone. Under 100 words."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Personalize the details for each client. ChatGPT does the writing; you add the human touch by knowing which client gets which version.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Social Media Captions for Transformation Posts
&lt;/h2&gt;

&lt;p&gt;Before-and-after posts are powerful marketing. But writing the caption? That's where most trainers freeze.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write an Instagram caption for a personal trainer posting a client transformation photo. The client lost 18 pounds over 4 months and says she feels stronger than she ever has. She gave permission to share her story. The caption should: celebrate her effort (not just the weight loss), mention that results come from consistency not perfection, and end with a call to action to DM for training inquiries. Keep it under 150 words. No excessive hashtag lists."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then add 5-6 relevant hashtags yourself: your city, your niche, and a couple broad ones. Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. FAQ Page for Your Website
&lt;/h2&gt;

&lt;p&gt;Most trainer websites have no FAQ section. That means potential clients are emailing you basic questions that could be answered in 30 seconds if you had the right page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm a personal trainer in [City] who specializes in working with busy adults over 40. Write 7 FAQ questions and answers for my website. Include questions about: pricing, session length, what to expect in the first session, online vs. in-person options, cancellation policy, how fast clients see results, and whether I give nutrition advice. Answers should be 2-3 sentences each, friendly and professional tone."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Fill in your actual answers after generating the draft. This takes 20 minutes and saves you hours of repetitive email answering every month.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Handling Cancellations and Rescheduling Professionally
&lt;/h2&gt;

&lt;p&gt;Last-minute cancellations are frustrating, and it's easy to send a passive-aggressive reply or nothing at all. Neither is good for the relationship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a reply to a personal training client who canceled their session 2 hours before the appointment (which is inside my 24-hour cancellation policy). The tone should be professional and firm but not cold — I want to keep the relationship. Remind them of the policy, let them know the session fee still applies this time, and offer to reschedule. Under 80 words."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having this message pre-drafted means you can respond quickly, professionally, and without letting your frustration leak into the message. That's worth a lot.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;Personal training is a relationship business. Every email, caption, and check-in message is either building trust or eroding it. The problem isn't that trainers don't care about these touchpoints — it's that there are only so many hours in a day.&lt;/p&gt;

&lt;p&gt;ChatGPT doesn't replace your expertise or your relationships. It just handles the blank-page problem so you can stay consistent without burning out.&lt;/p&gt;

&lt;p&gt;Start with one prompt. The check-in message is the easiest. Try it on your next client this week and see what happens.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Otto Brennan is an American expat based in Lisbon who writes about AI tools for small business owners. He's not a trainer or nutritionist — just a guy who pays attention to what's working.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>fitness</category>
      <category>smallbusiness</category>
      <category>ai</category>
    </item>
    <item>
      <title>5 Ways Solo Attorneys Are Using ChatGPT to Save 10+ Hours a Week</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 13:29:43 +0000</pubDate>
      <link>https://forem.com/ottobrennan/5-ways-solo-attorneys-are-using-chatgpt-to-save-10-hours-a-week-5a21</link>
      <guid>https://forem.com/ottobrennan/5-ways-solo-attorneys-are-using-chatgpt-to-save-10-hours-a-week-5a21</guid>
      <description>&lt;p&gt;If you're running a solo practice or a small firm, you already know the grind. You're the lawyer, the office manager, the marketing department, and sometimes the IT guy. There are only so many hours in the day, and most of them get swallowed up by tasks that aren't actually practicing law.&lt;/p&gt;

&lt;p&gt;I've spent the last year watching small business owners in Lisbon and back home in the States figure out how to use AI tools to claw back their time. Attorneys, it turns out, are some of the best candidates for this — because so much of your administrative and communication work follows predictable patterns.&lt;/p&gt;

&lt;p&gt;Here are five ways solo attorneys are using ChatGPT right now to save 10+ hours a week.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Drafting Client Emails in Under 2 Minutes
&lt;/h2&gt;

&lt;p&gt;You know the emails. Status updates, requests for documents, explaining why the case is taking longer than expected. They're important, but they're not complex — and they eat time.&lt;/p&gt;

&lt;p&gt;ChatGPT can draft these in seconds once you give it the right prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Write a professional but warm email to a client named [Name] explaining that we're still waiting on documents from the opposing party and expect to have an update within the next two weeks. The tone should be reassuring, not alarming. Keep it under 150 words."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll get a solid draft in about 10 seconds. Edit for accuracy, add any case-specific details, and send. What used to take 8 minutes now takes 90 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Summarizing Case Notes and Discovery Documents
&lt;/h2&gt;

&lt;p&gt;This one is a game-changer for attorneys who deal with high volumes of paperwork. Paste a wall of text — deposition notes, a long discovery response, meeting notes — and ask ChatGPT to summarize the key points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here are my notes from a client intake meeting. Summarize the key facts, the client's stated goals, any deadlines mentioned, and any red flags I should follow up on. Format it as a bulleted list."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Paste your notes after the prompt. You'll get a clean summary you can drop directly into your case management system. This alone saves some attorneys 3-4 hours a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important note:&lt;/strong&gt; Don't paste privileged or sensitive information into the free version of ChatGPT. Use ChatGPT Plus or a tool with a Business Associate Agreement if you're dealing with confidential client data. When in doubt, anonymize names and identifying details before pasting.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Writing First Drafts of Standard Letters
&lt;/h2&gt;

&lt;p&gt;Demand letters, follow-up notices, cease and desist letters, responses to opposing counsel — these follow standard structures. ChatGPT can produce a solid first draft in under a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt for a demand letter:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Draft a formal demand letter from an attorney to a tenant who has failed to pay rent for two months. The amount owed is $3,400. Include a 10-day deadline to pay or vacate. Tone should be firm and professional. I will review and edit before sending."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You're not replacing your legal judgment here. You're outsourcing the blank-page problem. The draft comes back, you review it, add jurisdiction-specific language, and adjust as needed. Most attorneys find they're editing 20% of the content rather than writing 100% from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Creating FAQ Content for Your Website
&lt;/h2&gt;

&lt;p&gt;Most attorney websites have weak FAQ pages — or none at all. That's a missed opportunity, because people searching for legal help are typing questions into Google, not keywords.&lt;/p&gt;

&lt;p&gt;ChatGPT can help you build out a solid FAQ section in an afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I'm a solo family law attorney in [State]. Write 8 FAQ questions and answers that a person going through a divorce for the first time would want answered. Keep the answers under 100 words each, written in plain English — no legal jargon. End each answer with a sentence that encourages them to schedule a consultation."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Generate a few batches of these, pick the best ones, and have a junior assistant or a friend proofread for accuracy. A strong FAQ page can drive organic traffic and pre-qualify clients before they ever call you.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Building Intake Questionnaires
&lt;/h2&gt;

&lt;p&gt;Before a client's first consultation, what do you actually need to know? Most attorneys have a mental list, but never took the time to formalize it into a clean intake form.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try this prompt:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create a new client intake questionnaire for a solo personal injury attorney. Include sections for: contact information, description of the incident, medical treatment received, insurance information, prior legal representation, and any photos or documentation they have. Keep questions clear and easy to answer for a non-lawyer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll get a complete draft questionnaire you can drop into a Google Form or your practice management software. Better intake means better-prepared consultations, fewer back-and-forth emails, and cases that start on the right foot.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;You didn't go to law school to spend half your day writing emails and formatting questionnaires. These tools won't replace your expertise — they'll clear the runway so you can actually use it.&lt;/p&gt;

&lt;p&gt;Start with whichever of these five feels most painful right now. Give ChatGPT one task, see how it does, and adjust your prompts until you're getting drafts you can actually work with.&lt;/p&gt;

&lt;p&gt;Ten hours a week is 500+ hours a year. That's time you could spend taking on more cases, or logging off before 7pm for once.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Otto Brennan is an American expat based in Lisbon who writes about AI tools for small business owners. He's not a lawyer and this isn't legal advice — just practical tools for running a smarter practice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>smallbusiness</category>
      <category>productivity</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Prompt Template Every Small Business Owner Needs (With Examples by Industry)</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 01:36:43 +0000</pubDate>
      <link>https://forem.com/ottobrennan/the-prompt-template-every-small-business-owner-needs-with-examples-by-industry-4olg</link>
      <guid>https://forem.com/ottobrennan/the-prompt-template-every-small-business-owner-needs-with-examples-by-industry-4olg</guid>
      <description>&lt;p&gt;I consult with small business owners on AI adoption. When someone tells me they tried ChatGPT and "it didn't really work," I ask them to show me how they used it.&lt;/p&gt;

&lt;p&gt;It's always the same thing: they typed a vague question and got a vague answer. They thought the tool was useless. The tool was fine — the instructions were the problem.&lt;/p&gt;

&lt;p&gt;AI works on specificity. Here's how to get specific.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Difference Between a Bad Prompt and a Good One
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Bad:&lt;/strong&gt; "Write me a social media post."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good:&lt;/strong&gt; "Write 3 Instagram captions for a photo of a before/after bathroom renovation I completed. My business serves homeowners in Phoenix, AZ. One caption should be professional, one should be casual and friendly, one should ask a question to encourage engagement. No more than 150 characters each."&lt;/p&gt;

&lt;p&gt;The second prompt takes 30 seconds longer to write. The output is ready to post. The first prompt gives you something you'll spend 10 minutes editing anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The rule:&lt;/strong&gt; The more you tell it, the less work you have to do with the result.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 4 Things Every Good Business Prompt Needs
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; — Who are you, what do you do, who are you talking to?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task&lt;/strong&gt; — What exactly do you need written/analyzed/organized?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints&lt;/strong&gt; — Length, tone, format, what to include or avoid&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output format&lt;/strong&gt; — A list? A paragraph? An email? A table?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't need all four every time. But when something comes out wrong, one of these is usually missing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Examples by Business Type
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Contractors / Home Services
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Writing estimates that sound professional but still feel personal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write an estimate summary email for a home renovation project. 
Details: replacing kitchen cabinets and countertops for a family home in [city]. 
Total: $8,400. Timeline: 2 weeks. 

Include: what's covered, timeline, payment terms (50% upfront, 50% on completion), 
and a friendly closing. Tone: professional but not corporate — I'm a small local 
contractor, not a big company. Under 200 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Retail (Online or In-Person)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Product descriptions that sound generic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a product description for [product name]. 
Key details: [material/features/dimensions/etc.]
Target customer: [e.g., "women in their 40s who want practical but stylish home goods"]
Tone: warm, slightly playful. Length: 80–120 words. 
Avoid: words like "premium," "luxurious," "high-quality" — say what makes it good, don't just claim it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Consultants / Coaches
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Writing proposals that feel like they came from a template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a consulting proposal for [client name/type]. 
They need help with: [specific problem].
I'm proposing: [3-session engagement / monthly retainer / one-time workshop]
Investment: $[amount]
Outcome I'm promising: [specific result]
Tone: direct and confident, not overselling. They're a small business owner 
who's skeptical of consultants. Under 300 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Restaurants / Food Businesses
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Captions and newsletters that all sound the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write 3 different Instagram captions for a photo of [dish]. 
My restaurant: [style — e.g., "casual Mexican, family-owned, in downtown Austin"].
Caption 1: Focus on the ingredients.
Caption 2: Tell a brief story about why this dish is on the menu.  
Caption 3: Ask a question to get engagement.
Each under 100 words. Include 3–5 relevant hashtags.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Real Estate Agents
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Listing descriptions that sound like every other listing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a listing description for this property:
- 3BR / 2BA, 1,850 sq ft
- Built 1978, fully renovated kitchen and baths
- Large backyard, quiet cul-de-sac
- School district: [name]
- Price: $485,000

Highlight what makes it stand out from similar homes in the area. 
Lead with the lifestyle, not the specs. Under 150 words. 
Avoid clichés like "must see," "charming," "cozy."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A Simple Template You Can Reuse
&lt;/h2&gt;

&lt;p&gt;If you're stuck, start here and fill in the brackets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm a [type of business] serving [type of customer] in [location].

Write a [type of content] for [specific purpose].

Include: [list the must-haves]
Tone: [professional / casual / direct / warm]
Length: [word/character count]
Avoid: [anything you don't want]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This template won't win a Pulitzer. But it'll get you a solid first draft in 30 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  What to Do When the Output Isn't Right
&lt;/h2&gt;

&lt;p&gt;Don't start over. Reply with a correction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Make it shorter."&lt;/li&gt;
&lt;li&gt;"Make the tone more casual — it sounds too corporate."&lt;/li&gt;
&lt;li&gt;"Add a call to action at the end."&lt;/li&gt;
&lt;li&gt;"The second paragraph is too vague — make it more specific about [topic]."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ChatGPT remembers your conversation. Each correction builds on the last.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;Most people use AI like a search engine: type a question, get an answer, done. &lt;/p&gt;

&lt;p&gt;Business writing is different. You know things ChatGPT doesn't: your customers, your voice, your specific situation. Your job is to put that knowledge into the prompt. The more you give it, the more useful the output.&lt;/p&gt;

&lt;p&gt;Once you get a prompt that works well, save it. That's now a permanent tool in your business — you can run it any time for 30 seconds of quality output.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I help small businesses set up simple AI workflows that actually save time. Questions? Drop them below.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>smallbusiness</category>
      <category>chatgpt</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How Cleaning Business Owners Are Using ChatGPT to Cut Admin Time in Half</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 01:31:31 +0000</pubDate>
      <link>https://forem.com/ottobrennan/how-cleaning-business-owners-are-using-chatgpt-to-cut-admin-time-in-half-2eg7</link>
      <guid>https://forem.com/ottobrennan/how-cleaning-business-owners-are-using-chatgpt-to-cut-admin-time-in-half-2eg7</guid>
      <description>&lt;p&gt;I run a small consulting practice helping local service businesses adopt AI tools. Cleaning companies are some of my favorite clients — they have genuine pain points and AI delivers fast, measurable results.&lt;/p&gt;

&lt;p&gt;This is a practical guide for cleaning business owners who want to use ChatGPT to cut admin time without hiring more staff.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Cleaning Businesses Waste the Most Time
&lt;/h2&gt;

&lt;p&gt;Before I talk about prompts, it's worth naming the specific problems:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Quoting&lt;/strong&gt; — Writing custom estimates that all look the same anyway&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client communication&lt;/strong&gt; — Responding to the same questions about availability, products, what's included&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team communication&lt;/strong&gt; — Writing schedules, checklists, instructions for each job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rebooking follow-ups&lt;/strong&gt; — Manually reaching out after a cleaning to rebook&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complaint handling&lt;/strong&gt; — Navigating the occasional unhappy client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of these is a writing task. ChatGPT is very good at writing tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 1: Custom Quotes in 3 Minutes
&lt;/h2&gt;

&lt;p&gt;Most cleaning businesses write quotes the same way every time but still treat it like a custom document.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a professional quote for a house cleaning job with these details:
- Client name: [name]
- Property: [bedrooms/bathrooms, approx sq ft]
- Service type: [standard clean / deep clean / move-out]
- Frequency: [one-time / weekly / bi-weekly / monthly]
- Special requests: [e.g., "client has two dogs, hardwood floors throughout"]
- Price: $[amount]

Include what's covered, any exclusions, and a professional closing. 
Keep it under 200 words. Friendly but businesslike tone.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feed this template to ChatGPT with the specific details for each job. You'll have a quote ready in under a minute.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus:&lt;/strong&gt; Create a few variants — one for high-end residential, one for rentals, one for commercial. Then it's copy-paste.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 2: FAQ Responses for Booking Inquiries
&lt;/h2&gt;

&lt;p&gt;If you get 20 inquiries a week, probably 15 of them ask the same 5 questions. Write AI-drafted answers once. Paste them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write short, friendly answers (under 80 words each) to these common 
cleaning service questions. Tone: warm, professional, like a local 
small business — not a corporate call center.

Questions:
1. Do you bring your own supplies?
2. What's your cancellation policy?
3. Do I need to be home?
4. Do you clean [specific thing — e.g., ovens, windows, garages]?
5. Are your cleaners background checked?

My actual answers: [fill in your real policies]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have 5 pre-written responses. Keep them in a Notes app or a simple Google Doc. Copy-paste as needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 3: Job Checklists for Your Cleaners
&lt;/h2&gt;

&lt;p&gt;Every property is different. But writing a custom checklist for each job is tedious. Let AI do it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a cleaning checklist for this job:
- Property type: [house / apartment / Airbnb / office]
- Size: [2BR/1BA / 3BR/2BA / etc]
- Special notes: [e.g., "client has a white couch, be careful", "focus on kitchen — it's an Airbnb turnover"]
- Service: [standard / deep clean / move-out]

Format as a clear checklist organized by room. Include any special 
priority items at the top.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your cleaners get a clear checklist for every job. You spend 2 minutes instead of 10.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 4: Re-booking Follow-Up Messages
&lt;/h2&gt;

&lt;p&gt;Most cleaning businesses leave money on the table by not following up after a job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a short text message (under 60 words) to send to a client after 
their cleaning to ask how it went and invite them to rebook. 
Friendly tone, not salesy. Their name is [name].
Optional: Include a soft mention that [specific thing they mentioned, 
e.g., "we noticed your kitchen needed extra attention — we can include 
that in a monthly plan"].
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send this 24 hours after every job. It takes 30 seconds to personalize. Most businesses don't do this at all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 5: Handling the Unhappy Client
&lt;/h2&gt;

&lt;p&gt;Someone's upset about a missed spot or a miscommunication. You're frustrated. You don't want to write an angry or defensive response. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A client sent me this message: [paste their message]

Write a response that:
- Acknowledges their frustration without being defensive
- Takes responsibility for any legitimate issue
- Offers a specific resolution (e.g., a free re-clean of the area in question)
- Keeps the door open for the relationship
- Sounds like a real person, not corporate customer service
Keep it under 120 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit before sending. Make sure it sounds like you. But it's a lot easier to edit than to write from scratch when you're frustrated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting This Up (30 Minutes)
&lt;/h2&gt;

&lt;p&gt;You don't need to set up anything complicated. Here's how to start today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open ChatGPT (free tier is fine for this)&lt;/li&gt;
&lt;li&gt;Copy the 5 prompts above into a Google Doc called "My AI Prompts"&lt;/li&gt;
&lt;li&gt;Fill in the bracketed parts with your actual business details&lt;/li&gt;
&lt;li&gt;Run each one once to see what it produces&lt;/li&gt;
&lt;li&gt;Save the outputs you like as templates&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. You now have AI-assisted drafts for your 5 most repetitive writing tasks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Realistic Outcome
&lt;/h2&gt;

&lt;p&gt;I've worked with three cleaning business owners on this. Typical results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Quote turnaround:&lt;/strong&gt; 20 minutes → under 5 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inquiry responses:&lt;/strong&gt; Thoughtful replies in 2 minutes vs. 10-15&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checklist creation:&lt;/strong&gt; 10 minutes → 2 minutes per job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-booking rate:&lt;/strong&gt; Increases when you actually follow up (obvious in retrospect)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this requires technical skills. It requires about an hour to set up and 2-5 minutes per use going forward.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about implementing this for your business? Drop them in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>smallbusiness</category>
      <category>chatgpt</category>
      <category>entrepreneur</category>
    </item>
    <item>
      <title>5 ChatGPT Prompts That Saved My Restaurant Client 8 Hours a Week</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 01:30:39 +0000</pubDate>
      <link>https://forem.com/ottobrennan/5-chatgpt-prompts-that-saved-my-restaurant-client-8-hours-a-week-2206</link>
      <guid>https://forem.com/ottobrennan/5-chatgpt-prompts-that-saved-my-restaurant-client-8-hours-a-week-2206</guid>
      <description>&lt;p&gt;My friend runs a small Italian restaurant. Nothing fancy — 40 seats, lunch and dinner, a menu that changes seasonally. She was spending every Sunday night writing the week's specials copy, answering the same Yelp reviews over and over, and updating her social media captions.&lt;/p&gt;

&lt;p&gt;She asked me to help her "do something with AI." I spent an afternoon with her. Two weeks later, she told me she'd reclaimed almost a full day of work every week.&lt;/p&gt;

&lt;p&gt;Here's exactly what we did.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With Most Restaurant AI Advice
&lt;/h2&gt;

&lt;p&gt;Most AI guides for restaurants talk about grand visions: AI that manages inventory, predicts demand, optimizes staffing. &lt;/p&gt;

&lt;p&gt;That's not what most small restaurant owners need. They need to spend less time writing and more time in the kitchen or with their customers.&lt;/p&gt;

&lt;p&gt;These 5 prompts focus on the writing tasks that eat up hours every week.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 1: Weekly Specials Copy
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The old way:&lt;/strong&gt; Staring at a blank document at 10pm Sunday, writing the same kind of sentences about pasta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I run a small Italian restaurant. Write menu descriptions for these specials 
for this week. Keep each description under 30 words, use sensory language, 
and make it sound approachable (not pretentious). Here are the dishes:

[paste your dish names and main ingredients]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example output for "Braised short rib, polenta, gremolata":&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Slow-braised short rib that falls apart at the touch, served over creamy stone-ground polenta with a bright lemon-herb gremolata. Rich, warm, unfussy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;She runs this every Sunday. Takes 5 minutes instead of 45.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 2: Yelp Review Responses
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The old way:&lt;/strong&gt; Writing individual responses to every review, trying to sound human and not like a corporate template.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I own a small restaurant. Write a response to this Yelp review that sounds 
warm and genuine (not corporate). If it's a complaint, acknowledge it 
specifically without being defensive. Keep it under 100 words.

Review: [paste the review]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;She now responds to every review. Before, she only responded to the bad ones, and even then reluctantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Edit the response before posting. Change one or two words to make it sound like &lt;em&gt;you&lt;/em&gt;, not like AI. It takes 30 seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 3: Social Media Captions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The old way:&lt;/strong&gt; Reusing the same three caption formats, running dry on ideas, posting inconsistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write 5 Instagram captions for a photo of [dish name] at my Italian restaurant 
in [city]. Mix tones: one nostalgic, one playful, one straightforward, one 
that highlights a specific ingredient. Include relevant hashtags. 
No quotes, emojis only if natural.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;She picks whichever one fits her mood. Batch this once a week for 10 posts in 20 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 4: Email Newsletter for Regulars
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The old way:&lt;/strong&gt; Not sending a newsletter because it was too much work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a short email newsletter for my restaurant's regulars. 
Include: a warm opening, this week's specials (I'll paste them), 
one behind-the-scenes note about [topic], and a soft call to make 
a reservation. Keep it conversational, under 250 words, no corporate speak.

This week's specials: [paste]
Behind-the-scenes topic: [e.g., "we just got our first truffle shipment of the season"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;She wasn't sending a newsletter before. Now she sends one every Tuesday. Open rates are 40%+ because it's a small, loyal list.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prompt 5: Handling Complaints Privately
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The old way:&lt;/strong&gt; Dreading the occasional angry DM and either ignoring it or over-apologizing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A customer sent me this message complaining about [their experience]. 
Write a response that: acknowledges their frustration, offers a genuine 
apology without admitting fault where it's unclear, and invites them back 
with a specific gesture (not a discount — something more personal). 
Keep it under 150 words.

Their message: [paste]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to win the argument. It's to turn a bad experience into a reason to come back. This prompt helps thread that needle.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Looks Like In Practice
&lt;/h2&gt;

&lt;p&gt;Here's her Sunday evening routine now:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run Prompt 1 with this week's specials → menu copy done (5 min)&lt;/li&gt;
&lt;li&gt;Run Prompt 3 for each dish with a photo she took that day → captions for the week (15 min)&lt;/li&gt;
&lt;li&gt;Run Prompt 4 for Tuesday newsletter → draft ready (10 min)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;During the week, Prompts 2 and 5 as needed.&lt;/p&gt;

&lt;p&gt;Total: 30 minutes instead of 3+ hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Key Thing
&lt;/h2&gt;

&lt;p&gt;None of these prompts replace her voice. They give her a starting point she can edit. &lt;/p&gt;

&lt;p&gt;The worst AI output is the one you post without reading. The best AI output is the one you polish into something that sounds like you, in 2 minutes instead of 20.&lt;/p&gt;

&lt;p&gt;That's the actual value here: not replacing your creativity, but giving you a draft fast enough that you actually do the thing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I work with small business owners on AI implementation. If you're spending hours on writing tasks and want to cut that down, feel free to reach out.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>smallbusiness</category>
      <category>chatgpt</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How Realtors Are Using AI to Write Better Listings in Half the Time</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 01:11:46 +0000</pubDate>
      <link>https://forem.com/ottobrennan/how-realtors-are-using-ai-to-write-better-listings-in-half-the-time-f3c</link>
      <guid>https://forem.com/ottobrennan/how-realtors-are-using-ai-to-write-better-listings-in-half-the-time-f3c</guid>
      <description>&lt;p&gt;Real estate agents write &lt;em&gt;a lot&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Listing descriptions. Follow-up emails. Neighborhood summaries. Client newsletters. Social media captions. Open house announcements. The average agent who's serious about their business is probably writing thousands of words a week — most of it repetitive, most of it taking longer than it should.&lt;/p&gt;

&lt;p&gt;AI writing tools, particularly ChatGPT, have been genuinely useful for agents I've worked with. Not because they replace good writing — but because they handle the repetitive parts so agents can focus on the parts that actually require their expertise.&lt;/p&gt;

&lt;p&gt;Here's how to use them without overcomplicating it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Listing Description Problem
&lt;/h2&gt;

&lt;p&gt;Most agents have a formula they follow for listing descriptions. Property facts in, polished copy out. The formula works — but executing it still takes time.&lt;/p&gt;

&lt;p&gt;Here's a prompt that short-circuits that process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a real estate listing description for a property with these details:

- Type: [single family / condo / townhouse]
- Bedrooms/Bathrooms: [X bed / X bath]
- Square footage: [X sq ft]
- Key features: [list the highlights — renovated kitchen, hardwood floors, etc.]
- Neighborhood: [neighborhood name, city]
- Neighborhood highlights: [good schools, walkable, near downtown, etc.]
- Target buyer: [first-time buyers / growing families / investors / empty nesters]
- Tone: [warm and inviting / professional / luxury / casual]
- Length: approximately 150 words
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output won't be perfect — it never is. But it's a solid first draft in 10 seconds rather than a blank page you stare at for 10 minutes. Your job becomes editing and injecting the specific details that only you know: the way the light hits the kitchen in the afternoon, the neighbor who maintains a stunning garden, the thing about the neighborhood that doesn't show up in any database.&lt;/p&gt;

&lt;p&gt;That's where you add value. Let AI handle the boilerplate.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Follow-Up Email That Doesn't Sound Like a Template
&lt;/h2&gt;

&lt;p&gt;"Just checking in!" is the enemy of good client communication.&lt;/p&gt;

&lt;p&gt;Agents who do follow-up well are specific. They reference something from the last conversation. They demonstrate they were listening. AI can help draft these — but only if you give it something to work with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a brief follow-up email to a buyer named [name]. We met at an open house for [property address]. They mentioned they're looking for [what they're looking for] and are hoping to close by [timeline]. They seemed most interested in [what they liked/didn't like about the property].

Keep it under 100 words. Warm and professional tone. Don't use "just checking in." Include a specific next step or question.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more specific you are, the better the draft. And the draft usually needs minimal editing because you've given it the key details.&lt;/p&gt;




&lt;h2&gt;
  
  
  Neighborhood Summaries (The Underused Opportunity)
&lt;/h2&gt;

&lt;p&gt;Buyers don't just buy properties — they buy into neighborhoods. Agents who can speak fluently about schools, walkability, local restaurants, weekend activities, and commute times close more deals.&lt;/p&gt;

&lt;p&gt;Most agents know this information. But writing it up takes time, and the result often sounds like a Wikipedia article.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write a neighborhood summary for [neighborhood name] in [city]. Use conversational, welcoming language — this is for buyers who are new to the area.

Include: [list what you know — nearby schools, commute to downtown, local restaurants/cafes, parks, any special character of the neighborhood]

Make it feel like a local is describing it to a friend, not a brochure. Approximately 200 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this as the foundation for your website copy, your buyer presentation deck, or even a short video script.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Social Calendar Problem
&lt;/h2&gt;

&lt;p&gt;"What do I post this week?"&lt;/p&gt;

&lt;p&gt;If you're asking this every Monday, you're wasting time. Here's how to solve it once a month:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create a 4-week social media content calendar for a real estate agent in [city/area]. My target audience is [buyers / sellers / both / investors].

For each week, suggest 3 posts:
1. One educational (tips, market info, buying/selling advice)
2. One community-focused (local business, neighborhood feature, area event)
3. One personal/brand-building (behind the scenes, client story, market insight)

Give me a topic for each post. Don't write the full captions — just the content direction.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that output, you can write the actual captions in batches — or use another AI prompt to write each one. The calendar gives you structure; the captions give you content.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AI Won't Replace
&lt;/h2&gt;

&lt;p&gt;AI won't replace the agent who knows that the listing on Elm Street has a foundation issue that isn't in any disclosure. It won't replace the agent who remembers that their client's real dealbreaker is a short commute, not the open concept kitchen they keep mentioning. It won't replace the relationship you've built with a client over three years of helping them find the right home.&lt;/p&gt;

&lt;p&gt;Those things require judgment, experience, and genuine human connection.&lt;/p&gt;

&lt;p&gt;What AI replaces is the two hours a week you spend staring at a blank email, rewriting listing descriptions from scratch, and trying to come up with something to post on Instagram.&lt;/p&gt;

&lt;p&gt;Use it for that. Save your expertise for the parts that actually matter.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>realestate</category>
      <category>productivity</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>The 5 ChatGPT Prompts I Give Every Small Business Owner on Day One</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 01:03:35 +0000</pubDate>
      <link>https://forem.com/ottobrennan/the-5-chatgpt-prompts-i-give-every-small-business-owner-on-day-one-5827</link>
      <guid>https://forem.com/ottobrennan/the-5-chatgpt-prompts-i-give-every-small-business-owner-on-day-one-5827</guid>
      <description>&lt;p&gt;When a small business owner asks me how to get started with ChatGPT, I don't send them a 40-prompt megalist.&lt;/p&gt;

&lt;p&gt;I send them five. The ones that work immediately. The ones that make them text me back within an hour saying "wait, why didn't I start using this sooner."&lt;/p&gt;

&lt;p&gt;Here they are.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The Customer Email Response Prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You're spending 30 minutes crafting a diplomatic response to a difficult customer. You know what you want to say but not how to say it without it coming out wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I run a [type of business]. A customer sent me this email:

[paste email]

Write me a professional, empathetic response that [what you want to achieve — e.g., "offers a refund without admitting fault" or "declines the request but keeps the relationship"]. Keep it under 150 words.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; You're doing the thinking. You know what outcome you want. ChatGPT handles the phrasing. It's like having an editor who's infinitely patient and never judges your first draft.&lt;/p&gt;

&lt;p&gt;Maria, the café owner I mentioned in my last post, uses a version of this for Yelp responses. She pastes in a negative review, tells ChatGPT the context, and asks it to write a public response. What used to take her 45 minutes now takes 5.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Social Media Caption Batch Prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You need to post 3x a week but "what do I even say" kills 20 minutes every time you open Instagram.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I own a [type of business] in [city/area]. My customers are [brief description].

Write 10 Instagram captions I can use this month. Mix these topics:
- A behind-the-scenes moment
- A customer benefit or result
- A seasonal/timely hook
- A question to drive comments
- Something personal/humanizing

Tone: [casual/professional/friendly/fun]. Keep each under 150 characters.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; You batch the thinking once and get content for two weeks. Edit the ones that don't fit. Use the rest.&lt;/p&gt;

&lt;p&gt;The key is being specific about your tone and customer. "Write Instagram captions for my business" gives you generic slop. "Write for a family-owned HVAC company in Phoenix whose customers are busy homeowners" gives you actually usable material.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The SOPs-from-Rambling Prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You have processes that exist only in your head. Training new employees means repeating yourself forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm going to describe my process for [task] in a messy, stream-of-consciousness way. Your job is to turn this into a clear, numbered step-by-step checklist that a new employee could follow on day one.

Here's my description:
[paste or type your rambling explanation]

Format it as: numbered steps, plain language, and flag anything that might need clarification.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; Keiko, the cleaning business owner, used exactly this to create five training SOPs in a single afternoon. She talked into her phone using voice-to-text, pasted the transcript, and got a professional procedure document.&lt;/p&gt;

&lt;p&gt;You don't have to be a good writer to use this. You just have to know your process.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Listing/Proposal Description Prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You need to write a compelling description of your product, service, or property — and you know it well but writing about it sounds stilted and weird.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I need to write a description for [what you're selling/offering]. Here are the key facts:

- [Fact 1]
- [Fact 2]
- [Fact 3]
- [Any context about the buyer/customer]

Write a [X word] description that highlights the benefits, not just the features. Make it feel [warm/professional/exciting]. Include a clear call to action at the end.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; David the realtor uses this for every listing. He inputs the specs, the neighborhood context, and who he thinks the buyer is. First draft is 80% there. He tweaks it in 5 minutes. Used to take him 30.&lt;/p&gt;

&lt;p&gt;This prompt works for anything you need to sell in words: service packages, product descriptions, website copy, proposals.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The "What Should I Say?" Prompt
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You're facing a situation — a hard conversation, a business decision, a negotiation — and you want to think it through but don't have a business advisor on speed dial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prompt:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm a [type of business owner] facing this situation:

[describe the situation]

What should I consider before making a decision? What are the strongest arguments for each option? What questions should I ask before deciding?

Don't make the decision for me — help me think through it clearly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt; This is the most underrated use of ChatGPT for business owners. It's not a decision-maker — it's a thinking partner. It forces you to articulate the problem clearly (which alone often helps) and reflects back angles you might not have considered.&lt;/p&gt;

&lt;p&gt;Used this myself when figuring out whether to expand a service offering. It didn't tell me what to do. But by the end of the conversation, I knew exactly what I wanted to do.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern You'll Notice
&lt;/h2&gt;

&lt;p&gt;All five prompts share a structure:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; — who you are, what your business does&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specific task&lt;/strong&gt; — not "write something" but "write this specific thing"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints&lt;/strong&gt; — length, tone, format&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What you want to achieve&lt;/strong&gt; — the outcome, not just the output&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the formula. Vague prompts get vague results. Specific prompts get usable results.&lt;/p&gt;

&lt;p&gt;Start with these five. Once they're part of your workflow, you'll naturally start adapting them and discovering more. But you don't need to — these five alone, used consistently, can save you 5-10 hours a month.&lt;/p&gt;

&lt;p&gt;That's time you can spend on the parts of your business that actually need you.&lt;/p&gt;

</description>
      <category>chatgpt</category>
      <category>smallbusiness</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Helped 3 Small Business Owners Use ChatGPT. Here's What Actually Worked (And What Didn't)</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Fri, 06 Mar 2026 01:02:32 +0000</pubDate>
      <link>https://forem.com/ottobrennan/i-helped-3-small-business-owners-use-chatgpt-heres-what-actually-worked-and-what-didnt-l4k</link>
      <guid>https://forem.com/ottobrennan/i-helped-3-small-business-owners-use-chatgpt-heres-what-actually-worked-and-what-didnt-l4k</guid>
      <description>&lt;p&gt;Most guides on using AI are written by developers, for developers.&lt;/p&gt;

&lt;p&gt;But the people who arguably need these tools most — small business owners — aren't going to wade through a Medium post about prompt engineering or a GitHub repo full of example notebooks.&lt;/p&gt;

&lt;p&gt;They need to know: &lt;em&gt;can I use this thing without breaking anything?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Over the past few months, I've been helping a few non-technical small business owners figure out how to actually use ChatGPT in their day-to-day work. Not to build anything. Not to automate their backend. Just to do the stuff that eats up their time — writing, responding to customers, figuring out what to post.&lt;/p&gt;

&lt;p&gt;Here's what I learned.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three People I Helped
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Maria&lt;/strong&gt; runs a small café. She's on her phone constantly but hasn't posted on Instagram in three weeks because "I just don't know what to say." She's also drowning in customer emails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;David&lt;/strong&gt; is a realtor. He writes a lot — listing descriptions, follow-up emails, newsletters. He's been doing this for 18 years and he's good at it, but it's slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keiko&lt;/strong&gt; owns a residential cleaning company. Her biggest pain point: new employee onboarding. She was re-explaining the same procedures over and over because nothing was written down.&lt;/p&gt;

&lt;p&gt;Three different people. Three different problems. Same tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Worked: The Surprisingly Mundane Stuff
&lt;/h2&gt;

&lt;p&gt;The first thing I noticed: none of them were impressed by what ChatGPT could do.&lt;/p&gt;

&lt;p&gt;They were impressed that it just &lt;em&gt;did it&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Maria typed: &lt;em&gt;"Write me 5 Instagram captions for a cozy café on a rainy day. We're in Austin. Friendly tone."&lt;/em&gt; She got five captions in about four seconds. She posted one, got thirty-something likes (good for her), and was in my DMs saying "this is magic."&lt;/p&gt;

&lt;p&gt;David asked it to write a listing description for a three-bedroom house. The first draft was generic. He typed &lt;em&gt;"make it more warm and family-focused, this neighborhood is known for great schools."&lt;/em&gt; Second draft was almost exactly what he'd have written, but in a third of the time.&lt;/p&gt;

&lt;p&gt;Keiko described her morning cleaning checklist verbally — just rambling into a voice-to-text tool on her phone — and then pasted the transcript into ChatGPT and asked it to &lt;em&gt;"make this into a step-by-step checklist a new employee can follow."&lt;/em&gt; She now has five SOPs she didn't have before.&lt;/p&gt;

&lt;p&gt;None of this is technically impressive. But it saved hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Didn't Work: Vague Prompts Get Vague Results
&lt;/h2&gt;

&lt;p&gt;The #1 problem I saw across all three of them: &lt;strong&gt;they asked ChatGPT to do things they hadn't fully thought through themselves.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Maria: &lt;em&gt;"Write me a caption."&lt;/em&gt; → Generic result, she doesn't like it, gives up.&lt;/p&gt;

&lt;p&gt;David: &lt;em&gt;"Write something about the Austin housing market."&lt;/em&gt; → He gets a wall of general facts, none of it useful.&lt;/p&gt;

&lt;p&gt;The fix is almost embarrassingly simple: &lt;strong&gt;be specific.&lt;/strong&gt; The more context you give, the better the output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ "Write me a caption"
✅ "Write a warm Instagram caption for my café promoting our new lavender latte.
    We're Austin-based, our vibe is cozy and community-focused, under 100 words."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once they got the hang of this, the outputs improved dramatically. I started calling it the "fill-in-the-blanks" approach — you're basically completing a template, not having a conversation with an oracle.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model That Actually Helped Them
&lt;/h2&gt;

&lt;p&gt;Here's the frame that made everything click for all three of them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatGPT is an intern who knows everything but has no context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's brilliant but knows nothing about your specific business, your tone, your customers, or what you're going for. Your job is to give it that context. Its job is to use it.&lt;/p&gt;

&lt;p&gt;Once they started thinking about it this way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They stopped hoping it would "just know" what they needed&lt;/li&gt;
&lt;li&gt;They started giving more context upfront&lt;/li&gt;
&lt;li&gt;They stopped accepting the first draft as the final answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is big. The first output is almost never what you'd actually use. Type "shorter" or "make it sound less formal" and you'll often get something much better in one extra step.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Prompt Categories That Actually Get Used
&lt;/h2&gt;

&lt;p&gt;Not all use cases are equal. Here's what they actually kept using after the novelty wore off:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily/weekly: Social media captions.&lt;/strong&gt; Fast, repeatable, immediately useful. Maria now does a weekly batch — she spends 20 minutes on Sunday generating captions for the week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A few times a month: Emails.&lt;/strong&gt; Follow-ups, complaint responses, promotions. David uses it for listing announcements and market update emails. He still edits them, but starts from a draft instead of a blank page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Occasionally: Documents and SOPs.&lt;/strong&gt; Keiko used it for the onboarding stuff. High one-time value, doesn't need to revisit it often.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rarely: Long-form content.&lt;/strong&gt; None of them found it great for things they cared about getting exactly right — like a heartfelt neighborhood spotlight or a personal story in a newsletter. They use it as a starting point at best.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Tell a Small Business Owner Getting Started
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with something you hate doing.&lt;/strong&gt; Not something exciting — something you dread. The email you've been putting off. The Instagram post you can't think of anything to say for. That's where you'll get the most relief.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Give it more context than you think it needs.&lt;/strong&gt; Your business name, your city, your tone, your customer, your goal. All of it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat it like a first draft generator.&lt;/strong&gt; Read it, edit it, send it. Not: read it, reject it because it's not perfect, go back to writing everything yourself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Build a small library of prompts that work.&lt;/strong&gt; Once you find a prompt that consistently gets you a good result, save it somewhere. That's your reusable template.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't try to learn everything at once.&lt;/strong&gt; Pick one use case — captions, or emails, or documents — and get good at that first.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  A Note for Developers Reading This
&lt;/h2&gt;

&lt;p&gt;If you work with clients, have family who runs a small business, or are building tools for non-technical users: this gap is real.&lt;/p&gt;

&lt;p&gt;The "AI for business" space is still mostly aimed at people comfortable with technology. The people who could benefit most — the restaurant owner, the realtor, the cleaning company — are left with YouTube tutorials that assume too much.&lt;/p&gt;

&lt;p&gt;If you're looking for a product to build or a consulting niche to own: there's a lot of value to be created in the translation layer between "ChatGPT exists" and "here's how to use it for your specific business."&lt;/p&gt;




&lt;p&gt;If you want a done-for-you starting point, I put together a pack of 50 ChatGPT prompts specifically for small business owners — social media, email, operations, marketing. No tech skills required. &lt;a href="https://ottobrennan.gumroad.com" rel="noopener noreferrer"&gt;You can find it here&lt;/a&gt; — I update it as I find new patterns that actually work in practice.&lt;/p&gt;

&lt;p&gt;What's working (or not working) for non-technical people you know? Drop it in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>chatgpt</category>
      <category>smallbusiness</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Python Web Scraping: The Production Guide (What the Tutorials Don't Tell You)</title>
      <dc:creator>Otto Brennan</dc:creator>
      <pubDate>Thu, 05 Mar 2026 21:46:53 +0000</pubDate>
      <link>https://forem.com/ottobrennan/python-web-scraping-the-production-guide-what-the-tutorials-dont-tell-you-49j9</link>
      <guid>https://forem.com/ottobrennan/python-web-scraping-the-production-guide-what-the-tutorials-dont-tell-you-49j9</guid>
      <description>&lt;p&gt;Python web scraping has a reputation problem. Every tutorial shows you the 10-line BeautifulSoup example that works great... until you try it on a real site.&lt;/p&gt;

&lt;p&gt;Then you hit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;403 Forbidden&lt;/li&gt;
&lt;li&gt;Empty responses (JavaScript-rendered content)&lt;/li&gt;
&lt;li&gt;Rate limiting after 50 requests&lt;/li&gt;
&lt;li&gt;CAPTCHAs&lt;/li&gt;
&lt;li&gt;IP bans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've built scrapers professionally for years. Here's what actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;p&gt;For most scraping projects you need exactly two things:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="n"&gt;beautifulsoup4&lt;/span&gt; &lt;span class="n"&gt;lxml&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;
&lt;span class="n"&gt;playwright&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;chromium&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;requests&lt;/code&gt; + &lt;code&gt;beautifulsoup4&lt;/code&gt; for static HTML. &lt;code&gt;playwright&lt;/code&gt; for JavaScript-heavy sites. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: The Right Way to Make Requests
&lt;/h2&gt;

&lt;p&gt;Most beginners do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://example.com/products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real sites will block you within minutes. Here's what you actually need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/html,application/xhtml+xml,application/xhtml;q=0.9,image/avif,image/webp,*/*;q=0.8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Language&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en-US,en;q=0.9&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Encoding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gzip, deflate, br&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DNT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Connection&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;keep-alive&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Upgrade-Insecure-Requests&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Random delay — looks human, avoids rate limits
&lt;/span&gt;            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Rate limited — back off exponentially
&lt;/span&gt;                &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rate limited. Waiting &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blocked on attempt &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Use a Session to maintain cookies across requests
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://example.com/products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a real browser User-Agent&lt;/strong&gt; — the default &lt;code&gt;python-requests/2.x.x&lt;/code&gt; is an instant flag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Random delays&lt;/strong&gt; — uniform distribution looks more human than fixed delays&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exponential backoff on 429s&lt;/strong&gt; — respect rate limits or get IP-banned&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sessions&lt;/strong&gt; — cookies persist, which many sites require for navigation&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Part 2: Parsing HTML with BeautifulSoup
&lt;/h2&gt;

&lt;p&gt;Once you have the HTML, parsing is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lxml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# lxml is faster than html.parser
&lt;/span&gt;    &lt;span class="n"&gt;products&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="c1"&gt;# Find all product cards
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.product-card&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.product-title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;href&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="c1"&gt;# Handle missing elements gracefully
&lt;/span&gt;            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;card&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.rating&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Always use &lt;code&gt;.select_one()&lt;/code&gt; with a fallback for optional fields.&lt;/strong&gt; Sites change their HTML. Your scraper needs to handle missing elements without crashing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Handling JavaScript-Rendered Sites
&lt;/h2&gt;

&lt;p&gt;~40% of modern sites render their content with JavaScript. &lt;code&gt;requests&lt;/code&gt; gets the empty skeleton. You need a real browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scrape_js_site&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="c1"&gt;# Use a real viewport size
&lt;/span&gt;            &lt;span class="n"&gt;viewport&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;width&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;height&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="c1"&gt;# Pass real browser headers
&lt;/span&gt;            &lt;span class="n"&gt;extra_http_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Language&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en-US,en;q=0.9&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Navigate and wait for content to load
&lt;/span&gt;        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.product-card&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Optional: scroll to trigger lazy loading
&lt;/span&gt;        &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;window.scrollTo(0, document.body.scrollHeight)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Get the fully-rendered HTML
&lt;/span&gt;        &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;content&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When to use Playwright vs requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;requests&lt;/code&gt;: static HTML, APIs, anything that works in &lt;code&gt;curl&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;playwright&lt;/code&gt;: React/Vue/Angular apps, infinite scroll, login flows, anything that needs JS&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 4: Pagination
&lt;/h2&gt;

&lt;p&gt;Most real scraping jobs involve multiple pages. Here's the pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scrape_all_pages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;all_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;page_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page_num&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_page&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

            &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lxml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_products&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;  &lt;span class="c1"&gt;# No more results
&lt;/span&gt;
            &lt;span class="n"&gt;all_items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Page &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page_num&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items (total: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Check for next page link
&lt;/span&gt;            &lt;span class="n"&gt;next_btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a[rel=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;next&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;next_btn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

            &lt;span class="n"&gt;page_num&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;all_items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 5: Storing the Data
&lt;/h2&gt;

&lt;p&gt;Don't just print results. Save them properly from the start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;save_to_sqlite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Create table from first item's keys
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; TEXT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CREATE TABLE IF NOT EXISTS &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, scraped_at TEXT)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
            &lt;span class="n"&gt;placeholders&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INSERT INTO &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;table_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; VALUES (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;placeholders&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;SQLite is underused for scraping. It's zero-setup, handles millions of rows, and makes deduplication easy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Mistake 1: Scraping what you should be using an API for&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check for a public API before scraping. &lt;code&gt;robots.txt&lt;/code&gt; before scraping too — it tells you what the site allows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 2: Not handling network errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Networks are flaky. Always wrap requests in try/except and implement retry logic (like the &lt;code&gt;get_page()&lt;/code&gt; function above).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 3: Running requests in a tight loop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No delay = IP ban within minutes. &lt;code&gt;time.sleep(random.uniform(1.5, 4.0))&lt;/code&gt; between requests is the minimum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mistake 4: Storing data as you go vs. collecting then storing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your scraper crashes on page 47, you lose everything. Store incrementally or checkpoint frequently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together
&lt;/h2&gt;

&lt;p&gt;Here's a complete, production-ready scraper for a static site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/html,application/xhtml+xml,application/xhtml;q=0.9,*/*;q=0.8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Accept-Language&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;en-US,en;q=0.5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;polite_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_delay&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Error fetching &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;scrape_site&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;results.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;start_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?page=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;polite_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;

            &lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lxml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Parse your target elements
&lt;/span&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.item&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;h2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;href&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;p&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;p&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt;

            &lt;span class="c1"&gt;# Check for next page
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.pagination .next&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Scraped page &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; total items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Save results
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fieldnames&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeheader&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writerows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Done. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items saved to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;output_file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;scrape_site&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://example.com/products&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This guide covers the fundamentals. Real-world projects also need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proxy rotation&lt;/strong&gt; when one IP isn't enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CAPTCHA handling&lt;/strong&gt; (2captcha, anti-captcha APIs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distributed scraping&lt;/strong&gt; across multiple machines&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental runs&lt;/strong&gt; that only fetch new data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; so you know when the site changed its structure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building production scrapers and want a head start, I packaged up the scripts I reuse across projects into a &lt;a href="https://ottobrennan.gumroad.com/l/kqfsv" rel="noopener noreferrer"&gt;Web Scraping Starter Kit&lt;/a&gt; — proxy rotation, Playwright integration, unified storage, and anti-detection headers included.&lt;/p&gt;

&lt;p&gt;Happy scraping. Be polite to the servers.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
