Reading JSON, Excel, HTML & SQL
Load data from JSON (read_json, lines=True, json_normalize), Excel workbooks (read_excel, sheet_name, ExcelFile), web tables (read_html) and databases (read_sql).
CSV is the most common format, but cricket data also arrives as JSON from web APIs (live scores, player profiles), as Excel workbooks from a team analyst, as HTML tables on stats websites, and from SQL databases behind a web app. Pandas has a read_* function for each one, and they all return the same thing — a DataFrame — so everything you learn later works no matter where the data came from.
Definition
The
pd.read_*functions are pandas' family of readers: each one understands a single file format or data source, parses it, and returns a DataFrame (read_htmlreturns a list of DataFrames).
read_json— JSON text: a list of records, a dict of columns, or "JSON Lines" (one JSON object per line).read_excel—.xlsx/.xlsworkbooks; one sheet at a time or several at once. Needs theopenpyxlpackage for.xlsx.read_html— finds every<table>in a web page. Needslxml(orbeautifulsoup4+html5lib).read_sql— runs an SQL query on a database connection and returns the result.
Syntax
python
pd.read_json(path_or_buffer, orient=None, lines=False, dtype=None)
pd.json_normalize(data, sep=".")
pd.read_excel(io, sheet_name=0, header=0, usecols=None, nrows=None)
pd.ExcelFile(io).sheet_names
pd.read_html(io, match=".+")
pd.read_sql(sql, con)| Parameter | Meaning | Default |
|---|---|---|
orient (read_json) | how the JSON is laid out: "records", "columns", "index", "split", "values" | None (auto-detect) |
lines (read_json) | True for JSON Lines — one object per line | False |
sheet_name (read_excel) | sheet name or position; a list → dict of DataFrames; None → all sheets | 0 (first sheet) |
header, usecols, nrows (read_excel) | same meaning as in read_csv | — |
match (read_html) | only return tables whose text matches this string/regex | ".+" (all) |
sql, con (read_sql) | the query (or table name) and an open database connection | required |
Example — read_json with a list of records
The most common API shape is a list of objects — one object per row:
python
import pandas as pd
import io
records = '[{"player": "Virat Kohli", "team": "RCB", "runs": 741}, {"player": "Travis Head", "team": "SRH", "runs": 567}, {"player": "Sunil Narine", "team": "KKR", "runs": 488}]'
df = pd.read_json(io.StringIO(records))
print(df)
print(df.dtypes)text
player team runs
0 Virat Kohli RCB 741
1 Travis Head SRH 567
2 Sunil Narine KKR 488
player str
team str
runs int64
dtype: objectHow it works: every object became a row and every key became a column. pandas auto-detects the layout, so orient is rarely needed when reading. A "dict of columns" such as {"player": {"0": "Virat Kohli", ...}, "runs": {"0": 741, ...}} is also recognised automatically.
Note (pandas 3): passing the JSON text directly —
pd.read_json(records)— no longer works. pandas treats any plain string as a file path and raisesFileNotFoundError: File [{"player": ... does not exist. Wrap literal JSON inio.StringIO(...)(pandas 2 only warned about this).
Example — JSON Lines and nested JSON
Logs and big exports often use JSON Lines: one JSON object per line, no surrounding [ ].
python
overs = """{"over": 1, "runs": 8}
{"over": 2, "runs": 3}
{"over": 3, "runs": 14}
"""
print(pd.read_json(io.StringIO(overs), lines=True))
nested = [{"player": "Virat Kohli", "stats": {"runs": 741, "sr": 154.7}},
{"player": "Travis Head", "stats": {"runs": 567, "sr": 191.55}}]
print(pd.json_normalize(nested))text
over runs
0 1 8
1 2 3
2 3 14
player stats.runs stats.sr
0 Virat Kohli 741 154.70
1 Travis Head 567 191.55How it works: lines=True reads each line as its own record. For nested JSON (a dict inside a dict), pd.json_normalize flattens the inner keys into columns named parent.child — here stats.runs and stats.sr. Note that json_normalize takes Python objects (lists/dicts), not text; use json.loads(text) first if you have a string.
Example — read_excel and sheet_name (not runnable in the browser)
Excel reading needs the openpyxl package, which the browser playground does not have, so this one is for your own computer (pip install openpyxl):
python
path = r"C:\Users\Admin\Desktop\data\ipl_stats.xlsx"
pd.ExcelFile(path).sheet_names # list the sheets first, e.g. ['Batting', 'Bowling']
batting = pd.read_excel(path) # first sheet (sheet_name=0)
bowling = pd.read_excel(path, sheet_name="Bowling")
second = pd.read_excel(path, sheet_name=1) # by position
both = pd.read_excel(path, sheet_name=["Batting", "Bowling"]) # dict of DataFrames
every = pd.read_excel(path, sheet_name=None) # ALL sheets, dictHow it works: a workbook can hold many sheets, so sheet_name picks which one. With a single name/position you get one DataFrame; with a list or None you get a dict like {"Batting": DataFrame, "Bowling": DataFrame}. pd.ExcelFile(path).sheet_names (used in Munna's notebook) is the quick way to discover sheet names before reading. All the read_csv ideas — header, usecols, nrows, skiprows, dtype, na_values — work the same here.
read_html and read_sql (brief)
pd.read_html(url_or_html) scans a page for <table> tags and returns a list of DataFrames, one per table:
python
tables = pd.read_html("https://en.wikipedia.org/wiki/List_of_Indian_Premier_League_seasons_and_results")
print(len(tables)) # how many tables were found
results = tables[0] # pick the one you need (use match="Winner" to filter)It needs lxml (or beautifulsoup4 + html5lib) and internet access, so it can't run in the playground.
pd.read_sql(query, connection) runs a query and gives you the result as a DataFrame. With Python's built-in sqlite3:
python
import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE players (name TEXT, team TEXT, runs INTEGER)")
conn.executemany("INSERT INTO players VALUES (?, ?, ?)",
[("Virat Kohli", "RCB", 741), ("Travis Head", "SRH", 567), ("Sunil Narine", "KKR", 488)])
df = pd.read_sql("SELECT name, runs FROM players WHERE runs > 500", conn)
print(df)
conn.close()text
name runs
0 Virat Kohli 741
1 Travis Head 567For PostgreSQL/MySQL you pass a SQLAlchemy engine instead of a sqlite3 connection. If you know SQL (see the SQL notes), this is the bridge: filter in SQL, analyse in pandas.
Comparison — which reader?
| Data looks like | Use | Returns |
|---|---|---|
a,b,c text lines | read_csv | DataFrame |
[{...}, {...}] or {"col": {...}} | read_json | DataFrame |
one {...} per line | read_json(..., lines=True) | DataFrame |
| nested dicts from an API | pd.json_normalize | DataFrame |
.xlsx workbook | read_excel(sheet_name=...) | DataFrame, or dict of them |
| web page with tables | read_html | list of DataFrames |
| database table / query | read_sql | DataFrame |
Common mistakes
- ❌
pd.read_json('[{"a": 1}]')in pandas 3 →FileNotFoundError. ✅pd.read_json(io.StringIO('[{"a": 1}]')). - ❌
ValueError: Expected object or value/ "Trailing data" on a JSON Lines file. ✅ Addlines=True. - ❌ Columns full of dicts after
read_jsonon nested data. ✅ Load withjson.loadsand flatten withpd.json_normalize. - ❌
ImportError: Missing optional dependency 'openpyxl'. ✅pip install openpyxl— pandas needs it for.xlsx. - ❌ Treating
read_html(...)as a DataFrame. ✅ It returns a list — index it:tables[0].
Interview tip
Q: How do you read every sheet of an Excel workbook at once?
A: pd.read_excel(path, sheet_name=None) returns a dict {sheet_name: DataFrame}. You can then loop over .items(), or combine them with pd.concat(sheets.values(), ignore_index=True) if they share the same columns.
Key points to remember
- Every pd.read_* function returns a DataFrame (read_html returns a list of DataFrames).
- read_json auto-detects records / columns layouts; lines=True reads JSON Lines (one object per line).
- In pandas 3 literal JSON text must be wrapped in io.StringIO — a plain string is treated as a file path.
- pd.json_normalize flattens nested dicts into columns like stats.runs.
- read_excel needs openpyxl; sheet_name picks a sheet by name or position, a list or None returns a dict.
- pd.ExcelFile(path).sheet_names lists the sheets in a workbook.
- read_sql(query, connection) runs SQL and returns the result as a DataFrame.
Try it yourself
read_json: records and column layouts
JSON Lines and nested JSON
read_excel with sheet_name (run on your own computer)
import pandas as pd
# Needs: pip install openpyxl (not available in the browser playground)
path = r"C:\Users\Admin\Desktop\data\ipl_stats.xlsx"
print(pd.ExcelFile(path).sheet_names) # e.g. ['Batting', 'Bowling']
batting = pd.read_excel(path) # first sheet
bowling = pd.read_excel(path, sheet_name="Bowling", usecols=["bowler", "wickets"])
all_sheets = pd.read_excel(path, sheet_name=None) # {'Batting': df, 'Bowling': df}
for name, sheet in all_sheets.items():
print(name, sheet.shape)Not runnable in the in-browser playground — try it locally.
Practice: load a live-score API response
Revision Flashcards
Tap a card to flip it and see the answer.
