惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Y
Y Combinator Blog
Google DeepMind News
Google DeepMind News
腾讯CDC
V
Visual Studio Blog
Engineering at Meta
Engineering at Meta
博客园 - 司徒正美
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
Vercel News
Vercel News
爱范儿
爱范儿
Last Week in AI
Last Week in AI
G
Google Developers Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
有赞技术团队
有赞技术团队
D
DataBreaches.Net
博客园_首页
J
Java Code Geeks
云风的 BLOG
云风的 BLOG
V
V2EX
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I Finally Got Reliable Data from Messy HTML Tables
zhongqiyue · 2026-06-19 · via DEV Community

zhongqiyue

I spent two weeks fighting a website that refused to play nice. Every scrape came back with missing columns, merged cells, or data that looked right but was subtly wrong. I tried BeautifulSoup, regex, pandas read_html – all the usual suspects. Nothing worked reliably because the HTML was a nightmare of inline styles, rowspan attributes, and inconsistent class names.

Let me back up. I needed to pull apartment rental data from a dozen property management sites. Most were fine, but one particular site (let's call it ShadowListings) had a table that looked like this when rendered:

Unit Price Beds Available
101 $1200 1 2024-03-01
102 $1500 2 2024-04-01

But the underlying HTML was a mess:

<table>
  <tr>
    <td rowspan="2">Unit</td>
    <td class="price-col">$1200</td>
    <td>1</td>
    <td><span>2024-03-01</span></td>
  </tr>
  <tr>
    <td>$1500</td>
    <td>2</td>
    <td>2024-04-01</td>
  </tr>
</table>

The first row had a rowspan="2" that threw off column alignment. And the class names changed between pages. My standard approach broke instantly.

What I Tried That Didn't Work

BeautifulSoup + manual column detection: I tried walking the DOM, tracking rowspan and colspan offsets. It worked for one page but broke on the next because the structure changed slightly.

Regex on the text: I thought I could just match price patterns and guess the rest. But dates were in multiple formats, and some rows had no price at all.

Pandas pd.read_html: It actually parsed the first table correctly, but then failed on sub-tables and returned a DataFrame with NaN everywhere because of misaligned cells.

Headless browser + OCR: Overkill, slow, and still needed heuristics to extract the actual values.

After a week of frustration, I started thinking: the human eye can instantly understand this table. Why? Because we don't parse HTML structure — we read the visual layout and use context. That's when I turned to AI.

What Eventually Worked: LLM-Powered Table Extraction

Instead of trying to fix the parser, I gave the raw HTML (or even a screenshot) to an LLM and asked it to extract the data. The trick was to use few-shot prompting with an example of the desired output.

Here's the approach I settled on:

  1. Fetch the HTML as text (or use a screenshot if the site uses JavaScript rendering).
  2. Strip out irrelevant tags (scripts, styles) to reduce token count.
  3. Ask the LLM to output a JSON array of objects, with explicit column names.
  4. Validate the output with a schema (e.g., price must be a string, date must match ISO format).

My first attempt used GPT-4, but even 3.5 worked fine for simple tables. The key was the prompt:

import openai

def extract_table_from_html(html_text):
    prompt = f"""
You are given raw HTML of a table. Extract the data into a JSON array of objects, where each object has keys: "unit", "price", "beds", "available_date".

Ignore any formatting issues like rowspan or colspan. Use the visual layout to infer columns.

HTML:
{html_text[:3000]}  # Trim to avoid token limits

Output ONLY valid JSON. No explanations.
"""
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    return response.choices[0].message.content

Wait, that naive version had problems: the LLM sometimes invented data or hallucinated columns. I needed few-shot examples and a validation step. Here's the refined version:

import json
from pydantic import BaseModel

def extract_and_validate(html):
    few_shot = """
Example 1:
Input: <table>...</table>
Output: [{"unit":"A1","price":"$1000","beds":"1","available_date":"2024-01-01"}]

Now do the same for this HTML:
"""
    prompt = few_shot + html[:4000]
    # Call LLM
    raw = call_llm(prompt)
    # Parse JSON safely
    try:
        data = json.loads(raw)
    except json.JSONDecodeError:
        # fallback: ask again with stricter prompt
        return retry_with_stricter(html)
    # Validate each row using Pydantic
    class Row(BaseModel):
        unit: str
        price: str
        beds: str
        available_date: str
    validated = [Row(**row).dict() for row in data]
    return validated

I ended up using the OpenAI API with a small wrapper I built. The cost was negligible - about $0.02 per page. Much cheaper than manual data entry.

Lessons Learned / Trade-offs

  • Latency: Each extraction takes 1-3 seconds. If you need to scrape 10,000 pages, that's hours and dollars. Not ideal for bulk.
  • Cost: At scale, it adds up. I used it only for the difficult 5% of pages where traditional parsing failed.
  • Accuracy: LLMs still make mistakes on ambiguous layouts. Adding few-shot examples helped, but sometimes it missed a column or merged two rows. I always run a sanity check (e.g., ensure price contains "$").
  • Feedback loop: I saved failures and retrained my prompt. Over time, extraction became more reliable.
  • Privacy: Sending HTML to a third-party API? Not always allowed. I used an internal LLM (via a service like the one at ai.interwestinfo.com) for sensitive pages.

What I'd Do Differently Next Time

  1. Try a smaller, fine-tuned model first. For a fixed table format, you could train a tiny model to output structured data directly, avoiding the overhead of a general LLM.
  2. Use vision models for rendered tables. If the HTML is too mangled, a screenshot can be passed to GPT-4 Vision, which often does better at understanding layout.
  3. Add human-in-the-loop for edge cases. I built a simple web UI that flagged uncertain extractions (e.g., confidence score < 0.8). Human review of those 10% kept quality high.
  4. Cache aggressively. I cached extracted data by page URL so I only paid once per page.

When NOT to Use This Approach

  • If your HTML is well-structured and consistent, use pandas or BeautifulSoup. They're faster, free, and deterministic.
  • If you need real-time extraction (e.g., live dashboard), LLM latency kills you.
  • If your data contains PII or trade secrets, think twice about sending it to an external API.

This technique saved my project. It turned an impossible scraping task into a weekend project. Now I treat LLMs as my "parser of last resort" — only for the messy cases that resist all logic.

What's your approach when traditional parsing fails? I'd love to hear if you've experimented with LLMs for data extraction.