Download fb_comments/export.py from bep40/fb-comments-crawler-space: direct link, hf CLI and curl.
- Browser
- Download file 4.45 kB
-
https://huggingface.co/spaces/bep40/fb-comments-crawler-space/resolve/main/fb_comments/export.py
- Command line
-
hf download hf://spaces/bep40/fb-comments-crawler-space/fb_comments/export.py
-
curl -L -o export.py https://huggingface.co/spaces/bep40/fb-comments-crawler-space/resolve/main/fb_comments/export.py
4.45 kB
| """Export helpers: post identifier parsing/resolution, CSV/JSON output.""" | |
| import csv | |
| import json | |
| import re | |
| import urllib.parse | |
| from .crawler import CommentCrawler, CommentCrawlerError, GRAPH as _GRAPH | |
| class ExportError(RuntimeError): | |
| pass | |
| def parse_post_identifier(url_or_id: str) -> str: | |
| """Return the Graph API object id for a Facebook post URL. | |
| Accepts: | |
| - ``https://www.facebook.com/<page>/posts/<story_fbid>`` | |
| - ``https://www.facebook.com/<page>/posts/pfbid0...`` | |
| - legacy ``https://www.facebook.com/permalink.php?story_fbid=...&id=...`` | |
| - ``https://www.facebook.com/photo.php?fbid=...&set=a...`` (photo comments) | |
| - plain ``<page_id>_<post_id>`` or ``<post_id>`` | |
| Returns e.g. ``123456789_101579...``. | |
| """ | |
| s = (url_or_id or "").strip() | |
| if not s: | |
| raise ExportError("Empty post URL/id.") | |
| if re.fullmatch(r"\d+_\d+", s): | |
| return s | |
| if re.fullmatch(r"\d+", s): | |
| return s | |
| if re.fullmatch(r"pfbid\w+", s) or re.fullmatch(r"pfbidp\w+", s): | |
| return s | |
| parsed = urllib.parse.urlparse(s if s.startswith("http") else "https://" + s) | |
| host = (parsed.netloc or "").lower() | |
| if "facebook.com" not in host: | |
| raise ExportError(f"Not a Facebook URL: {s}") | |
| path = parsed.path.strip("/") | |
| qs = urllib.parse.parse_qs(parsed.query) | |
| if path.startswith("permalink.php"): | |
| story = qs.get("story_fbid", [None])[0] | |
| page_id = qs.get("id", [None])[0] | |
| if story and page_id: | |
| return f"{page_id}_{story}" | |
| raise ExportError("permalink.php URL missing story_fbid/id parameters.") | |
| if path.startswith("photo.php"): | |
| fbid = qs.get("fbid", [None])[0] | |
| page_id = qs.get("id", [None])[0] | |
| if fbid and page_id: | |
| return f"{page_id}_{fbid}" | |
| raise ExportError("photo.php URL missing fbid/id parameters.") | |
| parts = path.split("/") | |
| if len(parts) >= 3 and parts[1] == "posts": | |
| return f"{parts[0]}_{parts[2]}" | |
| if len(parts) == 3 and parts[1] == "videos": | |
| return f"{parts[0]}_{parts[2]}" | |
| raise ExportError(f"Unrecognized Facebook post URL pattern: {s} (path={path})") | |
| def resolve_post_id(post_identifier: str, crawler: CommentCrawler) -> str: | |
| """Best-effort resolution of a handle to a stable object id. | |
| If the identifier is already ``<page_id>_<post_id>`` it is returned as-is. | |
| If it is a page handle + pfbid (e.g. ``MaiAnhTaiSinger_pfbid0...``) we call | |
| the post node and read ``id``. Falls back gracefully when the API can't. | |
| """ | |
| if re.fullmatch(r"\d+_\d+", post_identifier): | |
| return post_identifier | |
| page, _, story = post_identifier.partition("_") | |
| if not story: | |
| return post_identifier | |
| try: | |
| data = crawler._page(f"{_GRAPH}/{crawler.api_version}/{post_identifier}?" | |
| f"fields=id&access_token={crawler.token}") | |
| if data.get("id"): | |
| return data["id"] | |
| except CommentCrawlerError: | |
| pass | |
| return post_identifier | |
| def export(comments, out_csv: str | None = None, out_json: str | None = None): | |
| """Write comments (iterable of dicts) to CSV and/or JSON. | |
| Returns the list of dicts written (useful to count). | |
| """ | |
| rows = [] | |
| for c in comments: | |
| author = c.get("from") or {} | |
| rows.append( | |
| { | |
| "comment_id": c.get("id"), | |
| "parent_id": (c.get("parent") or {}).get("id"), | |
| "name": author.get("name"), | |
| "uid": author.get("id"), # PSID for page comments | |
| "message": c.get("message") or "", | |
| "created_time": c.get("created_time"), | |
| "like_count": c.get("like_count"), | |
| } | |
| ) | |
| if out_csv: | |
| with open(out_csv, "w", newline="", encoding="utf-8-sig") as f: | |
| writer = csv.DictWriter( | |
| f, | |
| fieldnames=["comment_id", "parent_id", "name", "uid", "message", "created_time", "like_count"], | |
| ) | |
| writer.writeheader() | |
| writer.writerows(rows) | |
| if out_json: | |
| with open(out_json, "w", encoding="utf-8") as f: | |
| json.dump(rows, f, ensure_ascii=False, indent=2) | |
| if not out_csv and not out_json: | |
| import sys | |
| for r in rows: | |
| sys.stdout.write( | |
| f"[{r['comment_id']}] {r['name']} ({r['uid']}): {r['message']}\n" | |
| ) | |
| return rows |