sigoden/aichat · error · anyhow::Error
Invalid extract selector
Error message
Invalid extract selector, {} What it means
When crawl options include an `extract` CSS selector, `crawl_page` parses it with `scraper::Selector::parse` and returns `anyhow!("Invalid extract selector, {}", err)` if parsing fails. The error includes the underlying selector-syntax error, so the selector used for extracting text from the crawled HTML is invalid.
Solutions
- Validate the selector in the browser devtools (`document.querySelector(...)`) before putting it in config.
- Fix syntax errors reported in the message: unbalanced brackets, quotes, or parentheses.
- Convert XPath/jQuery expressions to CSS selector syntax.
- Pre-test with `Selector::parse(...)` at config-load time to fail fast with a clear message.
Example fix
// before
let options = CrawlOptions { extract: Some("//article[@class='post']".into()), ..Default::default() };
// after
let options = CrawlOptions { extract: Some("article.post".into()), ..Default::default() }; Defensive patterns
Strategy: validation
Validate before calling
fn validate_selector(sel: &str) -> Result<(), String> {
match scraper::Selector::parse(sel) {
Ok(_) => Ok(()),
Err(e) => Err(format!("invalid extract selector {sel:?}: {e}")),
}
}
// call at config load: validate_selector(options.extract.as_deref().unwrap_or("*"))?; Try / catch
match crawl_website(&url, options).await {
Err(e) if e.to_string().starts_with("Invalid extract selector") => {
eprintln!("Fix --extract: {e}");
}
other => other?,
} Prevention
- Test selectors in browser devtools with document.querySelector before configuring.
- Use CSS selector syntax only — convert XPath/jQuery expressions.
- Validate the extract selector once at startup, not per crawl.
- Keep brackets/quotes balanced; prefer simple class/id selectors.
When it happens
Trigger: Passing a syntactically invalid CSS selector in `options.extract` to `crawl_website`/`crawl_page`, e.g. `div[foo=` (unclosed attribute), empty string, or unsupported pseudo-classes.
Common situations: Hand-written selectors in config with typos or unbalanced brackets/quotes; selectors copied from XPath or jQuery that aren't valid CSS (e.g. `//div[@id]`); scraper crate not supporting certain pseudo-elements.
Related errors
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/73e77b34983d7719.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/request.rs:414
for element in document.select(&selector) {
if let Some(href) = element.value().attr("href") {
let href = Url::parse(href).ok().or_else(|| location.join(href).ok());
match href {
None => continue,
Some(href) => {
if href.as_str().starts_with(location.as_str())
&& !should_exclude_link(href.path(), &options.exclude)
{
links.insert(href.path().to_string());
}
}
}
}
}
let text = if let Some(selector) = &options.extract {
let selector = Selector::parse(selector)
.map_err(|err| anyhow!("Invalid extract selector, {}", err))?;
document
.select(&selector)
.map(|v| html_to_md(&v.html()))
.collect::<Vec<String>>()
.join("\n\n")
} else {
html_to_md(&body)
};
Ok((path.to_string(), text, links.into_iter().collect()))
}
fn should_exclude_link(link: &str, exclude: &[String]) -> bool {
if link.contains("#") {
return true;
}
let parts: Vec<&str> = link.trim_end_matches('/').split('/').collect();
let name = parts.last().unwrap_or(&"").to_lowercase();View on GitHub (pinned to 82976d349a)