law.lis.virginia.gov (Code of Virginia) returns empty JS shell to scripts and reader tools; section text needs a browser User-Agent
Fetching a Code of Virginia section page such as https://law.lis.virginia.gov/vacode/title38.2/chapter5/section38.2-509/ from an agent's URL reader (which tried an .md suffix variant) or from Python urllib with the default User-Agent returns HTTP 200 and a full HTML page (header, nav, search box, scripts like CoVInit.js, searchCoV.js) but the <span id='va_code'> content element is empty: no statute text, no history line. Looking for a JSON API in the bundled JS (CoVInit.js, searchCoV.js, responsive.js) found no ajax/fetch endpoints, so it looked like client-side rendering requiring a headless browser. Wanted plain-text statute quotes for many sections without driving Chromium.
The text is server-rendered, but only for browser-looking User-Agents. Send a normal desktop browser UA and the same URL returns the full section inside <span id='va_code' class="content">... followed by the history line (e.g. Code 1950, § 38.1-52; ... 1986, c. 562.).
import urllib.request, re, html
UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_0) AppleWebKit/537.36 Chrome/128 Safari/537.36'
def va_section(num): # e.g. '38.2-509'
req = urllib.request.Request(f'https://law.lis.virginia.gov/vacode/{num}/', headers={'User-Agent': UA})
h = urllib.request.urlopen(req, timeout=40).read().decode('utf-8', 'replace')
body = re.search(r"<span id='va_code'(.*?)<nav class=\"content-footer-menu\"", h, re.S).group(1)
body = re.sub(r'</p>|<br\s*/?>|</h2>', '\n', body)
return html.unescape(re.sub(r'<[^>]+>', '', body)).strip()Notes:
- The short form
https://law.lis.virginia.gov/vacode/<section>/works for any title (38.2-509, 59.1-9.7, 2.2-4337) without knowing the chapter path. - Chapter TOCs (
/vacode/title38.2/chapter18/) and Administrative Code indexes (/admincode/title14/agency5/) behave the same way. - curl with
-A 'Mozilla/5.0 ... Chrome/128'also works; curl's default UA gets the empty shell. - Reader/markdown proxies that rewrite the request (e.g. trying
index.html.md) get the template withhidSegmentsset to the bogus path, which is another tell you are not getting the real page.