initial commit
This commit is contained in:
131
wowhead_scraper/parsers/quest.py
Normal file
131
wowhead_scraper/parsers/quest.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import re
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from parsers.common import (SIDES, entity_blob, gatherer, markup_links, markup_text, mapper, quick_facts,
|
||||
section_text, selector, text_after)
|
||||
|
||||
LISTVIEW_ID = "quests"
|
||||
CSV_FIELDS = ["id", "name", "level", "reqlevel", "side", "category", "category_id",
|
||||
"category2", "category2_id", "exp", "money", "url"]
|
||||
|
||||
_MAPPER_TYPES = {1: "npc", 2: "object", 3: "item"}
|
||||
|
||||
|
||||
def list_row(raw: Dict[str, Any], categories: Dict[int, tuple]) -> Dict[str, Any]:
|
||||
top_name, sub_names = categories.get(raw.get("category2"), (None, {}))
|
||||
return {
|
||||
"id": raw["id"],
|
||||
"name": raw.get("name"),
|
||||
"level": raw.get("level"),
|
||||
"reqlevel": raw.get("reqlevel"),
|
||||
"side": SIDES.get(raw.get("side")),
|
||||
"category": sub_names.get(raw.get("category")),
|
||||
"category_id": raw.get("category"),
|
||||
"category2": top_name,
|
||||
"category2_id": raw.get("category2"),
|
||||
"exp": raw.get("xp"),
|
||||
"money": raw.get("money"),
|
||||
}
|
||||
|
||||
|
||||
def parse_page(html: str, row: Dict[str, Any]) -> Dict[str, Any]:
|
||||
quest_id = int(row["id"])
|
||||
sel = selector(html)
|
||||
blob = entity_blob(html, "quest", quest_id) or {}
|
||||
names = gatherer(html)
|
||||
facts = quick_facts(html)
|
||||
|
||||
return {
|
||||
"id": quest_id,
|
||||
"name": blob.get("name", row.get("name")),
|
||||
"level": blob.get("level"),
|
||||
"reqlevel": blob.get("reqlevel"),
|
||||
"side": SIDES.get(blob.get("side")),
|
||||
"category": row.get("category"),
|
||||
"category2": row.get("category2"),
|
||||
"objectives": text_after(sel, "//h1[@class='heading-size-1']", stop_tags=("table", "div", "h2", "script")),
|
||||
"objective_items": _objective_items(sel),
|
||||
"description": section_text(sel, "Description"),
|
||||
"progress": section_text(sel, "Progress"),
|
||||
"completion": section_text(sel, "Completion"),
|
||||
"start": _fact_links(facts, "Start:"),
|
||||
"end": _fact_links(facts, "End:"),
|
||||
"locations": _locations(mapper(html)),
|
||||
"series": _series(sel, quest_id),
|
||||
"rewards": {
|
||||
"xp": blob.get("xp"),
|
||||
# the page's record omits money when it is 0
|
||||
"money": blob.get("money", 0),
|
||||
"item_choices": _items(blob.get("itemchoices"), names),
|
||||
"items": _items(blob.get("itemrewards"), names),
|
||||
"reputation": _reputation(sel, blob.get("reprewards")),
|
||||
},
|
||||
"quick_facts": [markup_text(f) for f in facts],
|
||||
"url": row.get("url"),
|
||||
}
|
||||
|
||||
|
||||
def _fact_links(facts: List[str], label: str) -> List[Dict[str, Any]]:
|
||||
for fact in facts:
|
||||
if markup_text(fact).startswith(label):
|
||||
return markup_links(fact)
|
||||
return []
|
||||
|
||||
|
||||
def _locations(mapper_config) -> List[Dict[str, Any]]:
|
||||
"""Flatten mapper points: who/what is where, and its role (start, end, objective)."""
|
||||
points = []
|
||||
for zone_id, zone in ((mapper_config or {}).get("objectives") or {}).items():
|
||||
for level in zone.get("levels", []):
|
||||
for p in level:
|
||||
points.append({
|
||||
"role": p.get("point"),
|
||||
"type": _MAPPER_TYPES.get(p.get("type"), p.get("type")),
|
||||
"id": p.get("id"),
|
||||
"name": p.get("name"),
|
||||
"zone_id": int(zone_id),
|
||||
"zone": zone.get("zone"),
|
||||
"coord": p.get("coord"),
|
||||
"coords": p.get("coords"),
|
||||
})
|
||||
return points
|
||||
|
||||
|
||||
def _objective_items(sel) -> List[Dict[str, Any]]:
|
||||
result = []
|
||||
for tr in sel.xpath("(//table[@class='icon-list'])[1]//tr"):
|
||||
href = tr.xpath(".//a/@href").get() or ""
|
||||
m = re.search(r"/(\w+)=(\d+)", href)
|
||||
# A count for items/kills; a standing such as "Friendly" for faction objectives.
|
||||
quantity = tr.attrib.get("data-icon-list-quantity", "1")
|
||||
result.append({
|
||||
"type": m.group(1) if m else None,
|
||||
"id": int(m.group(2)) if m else None,
|
||||
"name": " ".join(tr.xpath(".//a//text()").getall()).strip() or None,
|
||||
"quantity": int(quantity) if quantity.isdigit() else None,
|
||||
"standing": None if quantity.isdigit() else quantity,
|
||||
"provided": "(Provided)" in "".join(tr.xpath(".//td//text()").getall()),
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
def _series(sel, quest_id: int) -> List[Dict[str, Any]]:
|
||||
series = []
|
||||
for td in sel.xpath("//table[@class='series']//tr/td"):
|
||||
href = td.xpath(".//a/@href").get()
|
||||
m = re.search(r"quest=(\d+)", href or "")
|
||||
series.append({"id": int(m.group(1)) if m else quest_id,
|
||||
"name": " ".join(td.xpath(".//text()").getall()).strip()})
|
||||
return series
|
||||
|
||||
|
||||
def _items(pairs, names) -> List[Dict[str, Any]]:
|
||||
return [{"id": i, "name": names.get("item", {}).get(i), "count": c} for i, c in pairs or []]
|
||||
|
||||
|
||||
def _reputation(sel, pairs) -> List[Dict[str, Any]]:
|
||||
factions = {}
|
||||
for a in sel.xpath("//a[contains(@href, '/faction=')]"):
|
||||
m = re.search(r"faction=(\d+)", a.attrib["href"])
|
||||
factions[int(m.group(1))] = a.xpath("string()").get().strip()
|
||||
return [{"faction_id": f, "faction": factions.get(f), "value": v} for f, v in pairs or []]
|
||||
Reference in New Issue
Block a user