pyXcom 0.6.0__tar.gz → 0.7.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyxcom-0.6.0 → pyxcom-0.7.0}/CITATION.cff +3 -3
- {pyxcom-0.6.0 → pyxcom-0.7.0}/PKG-INFO +74 -14
- {pyxcom-0.6.0 → pyxcom-0.7.0}/README.md +72 -12
- pyxcom-0.7.0/docs/api.md +172 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/docs/publishing.md +13 -13
- pyxcom-0.7.0/examples/research_networks.py +78 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/pyproject.toml +3 -3
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/__init__.py +5 -2
- pyxcom-0.7.0/src/pyxcom/assets.py +114 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/cli.py +102 -4
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/client.py +54 -2
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/comments.py +81 -7
- pyxcom-0.7.0/src/pyxcom/discovery.py +498 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/layout.py +6 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/models.py +38 -0
- pyxcom-0.7.0/src/pyxcom/networks.py +905 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/parse.py +36 -4
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/schema.py +14 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/storage.py +134 -2
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/tables.py +147 -2
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/transport.py +124 -12
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/validate.py +30 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/validate_tables.py +28 -5
- pyxcom-0.7.0/tests/test_assets.py +115 -0
- pyxcom-0.7.0/tests/test_batch_report.py +42 -0
- pyxcom-0.7.0/tests/test_capability_cli.py +256 -0
- pyxcom-0.7.0/tests/test_capability_transport.py +44 -0
- pyxcom-0.7.0/tests/test_comment_transport.py +37 -0
- pyxcom-0.7.0/tests/test_comments.py +262 -0
- pyxcom-0.7.0/tests/test_direct_search.py +61 -0
- pyxcom-0.7.0/tests/test_discovery.py +365 -0
- pyxcom-0.7.0/tests/test_layout.py +170 -0
- pyxcom-0.7.0/tests/test_networks.py +537 -0
- pyxcom-0.7.0/tests/test_observations.py +203 -0
- pyxcom-0.7.0/tests/test_parsing.py +200 -0
- pyxcom-0.7.0/tests/test_public_api.py +176 -0
- pyxcom-0.7.0/tests/test_relational_integration.py +58 -0
- pyxcom-0.7.0/tests/test_schema.py +66 -0
- pyxcom-0.7.0/tests/test_search_storage.py +105 -0
- pyxcom-0.7.0/tests/test_tables.py +187 -0
- pyxcom-0.7.0/tests/test_transport.py +125 -0
- pyxcom-0.6.0/docs/api.md +0 -108
- {pyxcom-0.6.0 → pyxcom-0.7.0}/.gitignore +0 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/LICENSE +0 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/examples/basic.py +0 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/auth.py +0 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/batch.py +0 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/errors.py +0 -0
- {pyxcom-0.6.0 → pyxcom-0.7.0}/src/pyxcom/search.py +0 -0
|
@@ -6,12 +6,12 @@ authors:
|
|
|
6
6
|
- family-names: Wang
|
|
7
7
|
given-names: Haocheng
|
|
8
8
|
affiliation: Communication University of China
|
|
9
|
-
version: 0.
|
|
10
|
-
date-released: 2026-
|
|
9
|
+
version: 0.7.0
|
|
10
|
+
date-released: 2026-10-01
|
|
11
11
|
license: MIT
|
|
12
12
|
repository-code: "https://github.com/haochengw372-hash/pyXcom"
|
|
13
13
|
url: "https://github.com/haochengw372-hash/pyXcom"
|
|
14
|
-
abstract: "A Python package for collecting public X
|
|
14
|
+
abstract: "A Python package for collecting public X posts, native search results, reply branches, quotations, repost activity and observed follower/following networks, with linked tables, raw-response provenance and resumable snapshots."
|
|
15
15
|
keywords:
|
|
16
16
|
- computational communication
|
|
17
17
|
- social media
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyXcom
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Collect public X
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: Collect public X posts, search results, conversations, reposts, and observed follow networks with your browser session
|
|
5
5
|
Project-URL: Homepage, https://github.com/haochengw372-hash/pyXcom
|
|
6
6
|
Project-URL: Repository, https://github.com/haochengw372-hash/pyXcom
|
|
7
7
|
Author: Haocheng Wang
|
|
@@ -20,7 +20,9 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
|
|
21
21
|
**使用前请自行打开 Chrome 并登录 X。pyXcom 不会打开或控制浏览器,也不会替你登录。**
|
|
22
22
|
|
|
23
|
-
Collect public X user profiles,
|
|
23
|
+
Collect public X user profiles, posts, replies, search results and observed network relationships using your existing Chrome or Edge session. Post datasets contain three linked CSV tables: `users.csv`, `posts.csv` and `comments.csv`; network datasets have separate relationship and snapshot tables.
|
|
24
|
+
|
|
25
|
+
Version 0.7.0 adds native search, quote discovery, repost activity and follower/following snapshots. These capabilities have passed offline tests and bounded authenticated live endpoint tests. Collection remains limited to results returned by X; the tests do not establish complete historical or network coverage.
|
|
24
26
|
|
|
25
27
|
The interface follows [PykTok](https://github.com/dfreelon/pyktok)'s approachable get/save convention, with explicit parameters and resumable datasets. pyXcom is an independent implementation; it does not depend on PykTok or twikit.
|
|
26
28
|
|
|
@@ -81,18 +83,23 @@ Methods belong to `XClient`. Every method lists its accepted parameters explicit
|
|
|
81
83
|
| Multiple post IDs | `get_posts(post_ids)` | — | — |
|
|
82
84
|
| Authored main posts | `get_user_posts(handle, ...)` | `iter_user_posts(handle, ...)` | `save_user_posts(handle, output_dir, ...)` |
|
|
83
85
|
| Authored replies | `get_user_replies(handle, ...)` | `iter_user_replies(handle, ...)` | `save_user_replies(handle, output_dir, ...)` |
|
|
84
|
-
| Replies below a
|
|
86
|
+
| Replies below a post or reply | `get_post_comments(post_id_or_url, ...)` | `iter_post_comments(post_id_or_url, ...)` | `save_post_comments(post_id_or_url, output_dir, ...)` |
|
|
85
87
|
| Keyword matches | `get_search_posts(keyword, ...)` | `iter_search_posts(keyword, ...)` | `save_search_posts(keyword, output_dir, ...)` |
|
|
88
|
+
| Native cross-user query | `get_search_query(query, ...)` | `iter_search_query(query, ...)` | `save_search_query(query, output_dir, ...)` |
|
|
89
|
+
| Quotes of a post | `get_post_quotes(post_id_or_url, ...)` | `iter_post_quotes(post_id_or_url, ...)` | `save_post_quotes(post_id_or_url, output_dir, ...)` |
|
|
90
|
+
| Account repost activity | `get_user_reposts(handle, ...)` | `iter_user_reposts(handle, ...)` | `save_user_reposts(handle, output_dir, ...)` |
|
|
91
|
+
| Followers / following | `get_followers(user_id, ...)` / `get_following(user_id, ...)` | `iter_followers(user_id, ...)` / `iter_following(user_id, ...)` | `save_followers(user_id, output_dir, ...)` / `save_following(user_id, output_dir, ...)` |
|
|
92
|
+
| Reposter user list | `get_post_reposters(post_id_or_url, ...)` | `iter_post_reposters(post_id_or_url, ...)` | `save_post_reposters(post_id_or_url, output_dir, ...)` |
|
|
86
93
|
| Main posts + replies | — | — | `save_user_activity(handle, output_dir, ...)` |
|
|
87
94
|
| Multiple accounts | — | — | `save_users_activity(handles, output_dir, ...)` |
|
|
88
95
|
|
|
89
|
-
- `get_*` returns a `Profile`, `Post`, or `list[
|
|
96
|
+
- `get_*` returns a `Profile`, `Post`, `list[Post]`, or network `list[Profile]`; it does not write a dataset.
|
|
90
97
|
- `iter_*` returns an iterator. Requests happen as it is consumed.
|
|
91
|
-
- `save_*`
|
|
98
|
+
- `save_*` returns `CollectionResult` with `output_dir`, `post_count`, `pages_fetched`, `complete`, and `reason`. For post datasets `post_count` counts unique records, including replies; for network datasets this compatibility field counts observed users. Consult each manifest for table counts and coverage.
|
|
92
99
|
- Parameters: `handle` for one account, `handles` for several, `keyword` for search, `output_dir` for saved datasets, and `since`/`until` for dates. Options are keyword-only.
|
|
93
100
|
- Dates are UTC: `since` inclusive, `until` exclusive. `max_pages` and `limit` must be positive integers or `None`. Saved timeline/mirror `limit` is a page-boundary stopping threshold, so a final page can exceed it; iterator limits are exact.
|
|
94
101
|
|
|
95
|
-
See [API reference](docs/api.md) for signatures, return types and compatibility names.
|
|
102
|
+
See [API reference](https://github.com/haochengw372-hash/pyXcom/blob/main/docs/api.md) for signatures, return types and compatibility names.
|
|
96
103
|
|
|
97
104
|
### Search one account
|
|
98
105
|
|
|
@@ -110,6 +117,40 @@ with XClient(profile="Default") as client:
|
|
|
110
117
|
|
|
111
118
|
Direct account search scans main posts and authored replies, then matches a literal word/phrase without case sensitivity. Matching posts do not represent deduplicated real-world events. Cross-user search requires an explicit `mirror_base`; it sends search terms to that mirror and retrieves discovered post details from X. No mirror is enabled by default.
|
|
112
119
|
|
|
120
|
+
### Native search, quotes and repost activity
|
|
121
|
+
|
|
122
|
+
`*_search_query` submits X search syntax to the native Latest search endpoint; it does not require or use a mirror. `*_search_posts` retains its account/literal behavior above. Use a separate output directory for each query or source:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
with XClient(profile="Default") as client:
|
|
126
|
+
result = client.save_search_query(
|
|
127
|
+
'("usage reset" OR "rate limit reset") (OpenAI OR Codex)',
|
|
128
|
+
"output/reset_search", since="2025-09-29", until="2026-09-30",
|
|
129
|
+
max_pages=2, limit=100,
|
|
130
|
+
)
|
|
131
|
+
print(result.complete, result.reason)
|
|
132
|
+
client.save_post_quotes("1973931546550894681", "output/reset_quotes", max_pages=2)
|
|
133
|
+
client.save_user_reposts("thsottiaux", "output/tibo_reposts", max_pages=2)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Quote discovery uses native search followed by a check of `quoted_post_id`; zero matches do not establish zero quotes. Repost activity is collected from visible account timeline wrappers, preserving the repost ID and its action timestamp separately from the original post ID/time. A flattened timeline that only returns originals cannot establish action times and reports `repost_activity_unavailable` with an incomplete result. It is not a complete historical repost archive. Search sorting, visibility and pagination remain controlled by X.
|
|
137
|
+
|
|
138
|
+
### Follow networks and reposter lists
|
|
139
|
+
|
|
140
|
+
Resolve a handle with `get_user(handle).id`, then pass that stable numeric ID to follower/following methods:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
with XClient(profile="Default") as client:
|
|
144
|
+
user_id = client.get_user("thsottiaux").id
|
|
145
|
+
client.save_followers(user_id, "output/tibo_network", max_pages=2, snapshot_id="wave-1")
|
|
146
|
+
client.save_following(user_id, "output/tibo_network", max_pages=2, snapshot_id="wave-1")
|
|
147
|
+
client.save_post_reposters("1973931546550894681", "output/reset_reposters", max_pages=2)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Follow edges point from follower to followed account. `follow_edges.csv`, `reposters.csv`, `user_snapshots.csv` and `network_manifest.json` preserve observed relationships, user profiles and coverage per snapshot. A reposter list identifies who visibly reposted a post, but its `action_time_utc` is unknown. Use account repost activity when the wrapper supplies an action time; never substitute the original post time.
|
|
151
|
+
|
|
152
|
+
Network saves resume a recoverable unfinished snapshot. Once completed or stopped by a terminal bad cursor, calling again without `snapshot_id` creates a new observation; explicitly reusing a completed ID returns that saved snapshot. Memory/iterator methods raise endpoint, parse or rate-limit errors and retain coverage in `last_network_collection`; hitting a budget returns the observed prefix. Saves return an incomplete result and preserve checkpoints on source failures. Network and post collections require separate output directories to protect their different `users.csv` exports. Follow observations cannot establish when a relationship began or reconstruct a historical follow network. Keep collection dates in the analysis. See [examples/research_networks.py](examples/research_networks.py) for a small collection recipe; importing it does not collect data.
|
|
153
|
+
|
|
113
154
|
### Collect several accounts
|
|
114
155
|
|
|
115
156
|
```python
|
|
@@ -124,7 +165,7 @@ with XClient(profile="Default") as client:
|
|
|
124
165
|
|
|
125
166
|
Repeat to resume. `rounds=0, wait_on_rate_limit=True` keeps running and waits for rate windows when needed. `progress=callback` receives status dictionaries.
|
|
126
167
|
|
|
127
|
-
## Collect a
|
|
168
|
+
## Collect a post or reply and nested comments
|
|
128
169
|
|
|
129
170
|
```python
|
|
130
171
|
with XClient(profile="Default") as client:
|
|
@@ -144,7 +185,7 @@ pyxcom post-comments 1973931546550894681 --profile Default \
|
|
|
144
185
|
--output-dir output/tibo_conversation
|
|
145
186
|
```
|
|
146
187
|
|
|
147
|
-
The
|
|
188
|
+
The seed can be a main post or a reply. Level 1 replies directly to that seed; level 2 replies to those comments. Main seeds belong in `posts.csv`; reply seeds remain replies in `comments.csv`. `seed_post_id` and `seed_relative_depth` describe this branch; `root_post_id`, `parent_post_id` and `depth` retain actual conversation relationships and do not relabel a reply as a platform root. Ancestors returned as context are stored in `context_posts.csv`, not counted as collected descendants. `since`/`until` filter descendant timestamps; traversal retains the ancestry needed to reach in-window descendants. Quoted content, unrelated recommendations and comments beyond the requested depth are excluded. Profile details absent from the response remain unavailable.
|
|
148
189
|
|
|
149
190
|
`max_comments` is an exact total comment cap, excluding the root. `max_pages` caps conversation requests per call. Repeat the same root/depth/output to resume saved branches and cursors; raise the comment cap to continue a capped dataset. Pending page records are retained, so stopping midway through a page does not skip them. Changing depth requires a separate dataset. Python accepts `None` for unlimited comment/page budgets; depth must be positive.
|
|
150
191
|
|
|
@@ -169,12 +210,16 @@ output/tibo_year/
|
|
|
169
210
|
|
|
170
211
|
Replies to oneself remain comments. A quote within a reply retains its quote link in `comments.csv`. Direct replies are depth 1; replies to those are depth 2. Missing ancestry leaves `depth` empty with a `depth_status`; `parent_in_dataset` and `root_in_dataset` indicate observed relationships. Do not interpret missing parents as first-level replies.
|
|
171
212
|
|
|
172
|
-
`user_replies` means replies **authored by the selected account**. Use `*_post_comments` to collect replies below a particular
|
|
213
|
+
`user_replies` means replies **authored by the selected account**. Use `*_post_comments` to collect replies below a particular post or reply from all visible authors, including nested replies. Neither endpoint establishes full conversation coverage.
|
|
173
214
|
|
|
174
215
|
CSV conventions: UTF-8 with BOM, snake_case columns, string IDs (import as text in Excel), JSON arrays for list fields, empty values for unavailable scalars, UTC timestamp fields. Users without a saved profile have `profile_available=false`. Engagement counts are snapshots; unknown values are not replaced with zero.
|
|
175
216
|
|
|
176
217
|
If source records include pure reposts, an additional `interactions.csv` preserves them. The current authored-account collector does not establish complete repost activity. `manifest.json` records schema/layout versions, table counts, collection scope, relationship coverage and hashes. `.pyxcom/` must be retained for resuming; the three CSV files can be shared independently for analysis.
|
|
177
218
|
|
|
219
|
+
Saved collection pages also retain platform response JSON under `.pyxcom/raw/` (network pages under their snapshot state) and query provenance in `.pyxcom/collection_log.jsonl`. Repeated post observations keep returned engagement metrics in `metric_snapshots.csv`, with source observations in `.pyxcom/observations.jsonl` and metric history in `.pyxcom/metric_snapshots.jsonl`. Unreturned counts remain unavailable. These are observations at retrieval time, not historical metrics at publication. Credentials are not included in the response archive.
|
|
220
|
+
|
|
221
|
+
When source records contain reply, quote or repost relationships, `post_edges.csv` exports their directions, source/target post and user IDs, action and observation times, and target availability/resolution flags. An unavailable target is retained; its user ID stays empty if unresolved. Known target authors can appear in `users.csv` as stubs with `profile_available=false`. Context-only records do not create source edges. Relationship, context and metric exports appear when observations provide the corresponding data.
|
|
222
|
+
|
|
178
223
|
## Command line
|
|
179
224
|
|
|
180
225
|
CLI task names correspond to the Python methods:
|
|
@@ -189,10 +234,19 @@ pyxcom search-posts reset --handle thsottiaux --profile Default \
|
|
|
189
234
|
--since 2025-09-29 --until 2026-09-30 --output-dir output/tibo_reset
|
|
190
235
|
pyxcom users-activity --handles OpenAI AnthropicAI --profile Default \
|
|
191
236
|
--since 2025-09-29 --until 2026-09-30 --output-dir output/ai_year
|
|
237
|
+
pyxcom search-query '("usage reset" OR "rate limit reset") Codex' --profile Default \
|
|
238
|
+
--since 2025-09-29 --until 2026-09-30 --max-pages 2 --output-dir output/reset_search
|
|
239
|
+
pyxcom post-quotes 1973931546550894681 --max-pages 2 --output-dir output/reset_quotes
|
|
240
|
+
pyxcom user-reposts thsottiaux --max-pages 2 --output-dir output/tibo_reposts
|
|
241
|
+
pyxcom user-followers USER_ID --max-pages 2 --snapshot-id wave-1 --output-dir output/network
|
|
242
|
+
pyxcom user-following USER_ID --max-pages 2 --snapshot-id wave-1 --output-dir output/network
|
|
243
|
+
pyxcom post-reposters 1973931546550894681 --max-pages 2 --output-dir output/reposters
|
|
192
244
|
```
|
|
193
245
|
|
|
194
246
|
Add `--proxy URL` if your network requires one. Use `pyxcom COMMAND --help` for task options. Individual `user`, `post`, and `raw-post` commands accept `--output FILE` for a JSON file.
|
|
195
247
|
|
|
248
|
+
`post-comments --max-comments all --max-pages all` removes comment/page caps; `--max-depth` still requires a positive integer (default 2). New discovery/network commands also accept `all` for `--max-pages` and `--limit`. Unlimited budgets do not remove visibility or rate limits.
|
|
249
|
+
|
|
196
250
|
## Export, migration and verification
|
|
197
251
|
|
|
198
252
|
These operations work offline, without browser credentials:
|
|
@@ -226,17 +280,17 @@ X endpoints may change or impose limits. pyXcom discovers current GraphQL query
|
|
|
226
280
|
|
|
227
281
|
## License
|
|
228
282
|
|
|
229
|
-
pyXcom is released under the [MIT License](LICENSE). Copyright © 2026 Haocheng Wang.
|
|
283
|
+
pyXcom is released under the [MIT License](https://github.com/haochengw372-hash/pyXcom/blob/main/LICENSE). Copyright © 2026 Haocheng Wang.
|
|
230
284
|
|
|
231
285
|
## Citation
|
|
232
286
|
|
|
233
287
|
**Author:** Haocheng Wang, Communication University of China.
|
|
234
288
|
|
|
235
|
-
If you use pyXcom in a paper, thesis, dataset, or other research output, please cite the software and report the version used. GitHub's **Cite this repository** menu reads the machine-readable [CITATION.cff](CITATION.cff).
|
|
289
|
+
If you use pyXcom in a paper, thesis, dataset, or other research output, please cite the software and report the version used. GitHub's **Cite this repository** menu reads the machine-readable [CITATION.cff](https://github.com/haochengw372-hash/pyXcom/blob/main/CITATION.cff).
|
|
236
290
|
|
|
237
291
|
**Suggested reference**
|
|
238
292
|
|
|
239
|
-
Wang, H. (2026). *pyXcom: Structured and auditable X data collection for communication research* (Version 0.
|
|
293
|
+
Wang, H. (2026). *pyXcom: Structured and auditable X data collection for communication research* (Version 0.7.0) [Computer software]. https://github.com/haochengw372-hash/pyXcom
|
|
240
294
|
|
|
241
295
|
**BibTeX**
|
|
242
296
|
|
|
@@ -245,9 +299,15 @@ Wang, H. (2026). *pyXcom: Structured and auditable X data collection for communi
|
|
|
245
299
|
author = {Wang, Haocheng},
|
|
246
300
|
title = {{pyXcom}: Structured and Auditable X Data Collection for Communication Research},
|
|
247
301
|
year = {2026},
|
|
248
|
-
version = {0.
|
|
302
|
+
version = {0.7.0},
|
|
249
303
|
url = {https://github.com/haochengw372-hash/pyXcom}
|
|
250
304
|
}
|
|
251
305
|
```
|
|
252
306
|
|
|
253
307
|
For reproducible reporting, also describe the collection dates, account or keyword scope, comment-depth limits, package version, and coverage/stop reasons recorded in the output manifest. No DOI or published-paper citation is currently assigned; this reference cites the software itself. Citation is appreciated and does not add a condition to the MIT license.
|
|
308
|
+
|
|
309
|
+
For longitudinal profile comparisons use `user_snapshots.csv`; `users.csv` consolidates known profile fields. Canonical post tables keep the first saved observation for each ID; later returned text and metrics remain in observation archives and metric snapshots.
|
|
310
|
+
|
|
311
|
+
Offline development checks: `PYTHONPATH=src python -m pytest -q` and `ruff check src tests examples`. The source distribution includes tests; testing tools are development-only, not runtime dependencies.
|
|
312
|
+
|
|
313
|
+
Live validation: current X SearchTimeline/Followers use POST read queries; Retweeters is discovered from public lazy-loaded TweetActivity assets. If a search source returns posts outside explicit dates, they are excluded locally and `manifest.json.date_scope` records the mismatch. An empty bounded result with this warning does not establish historical absence. Native reposts generate only repost edges; embedded quote/reply context is not attributed as another action by the reposter.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
**使用前请自行打开 Chrome 并登录 X。pyXcom 不会打开或控制浏览器,也不会替你登录。**
|
|
4
4
|
|
|
5
|
-
Collect public X user profiles,
|
|
5
|
+
Collect public X user profiles, posts, replies, search results and observed network relationships using your existing Chrome or Edge session. Post datasets contain three linked CSV tables: `users.csv`, `posts.csv` and `comments.csv`; network datasets have separate relationship and snapshot tables.
|
|
6
|
+
|
|
7
|
+
Version 0.7.0 adds native search, quote discovery, repost activity and follower/following snapshots. These capabilities have passed offline tests and bounded authenticated live endpoint tests. Collection remains limited to results returned by X; the tests do not establish complete historical or network coverage.
|
|
6
8
|
|
|
7
9
|
The interface follows [PykTok](https://github.com/dfreelon/pyktok)'s approachable get/save convention, with explicit parameters and resumable datasets. pyXcom is an independent implementation; it does not depend on PykTok or twikit.
|
|
8
10
|
|
|
@@ -63,18 +65,23 @@ Methods belong to `XClient`. Every method lists its accepted parameters explicit
|
|
|
63
65
|
| Multiple post IDs | `get_posts(post_ids)` | — | — |
|
|
64
66
|
| Authored main posts | `get_user_posts(handle, ...)` | `iter_user_posts(handle, ...)` | `save_user_posts(handle, output_dir, ...)` |
|
|
65
67
|
| Authored replies | `get_user_replies(handle, ...)` | `iter_user_replies(handle, ...)` | `save_user_replies(handle, output_dir, ...)` |
|
|
66
|
-
| Replies below a
|
|
68
|
+
| Replies below a post or reply | `get_post_comments(post_id_or_url, ...)` | `iter_post_comments(post_id_or_url, ...)` | `save_post_comments(post_id_or_url, output_dir, ...)` |
|
|
67
69
|
| Keyword matches | `get_search_posts(keyword, ...)` | `iter_search_posts(keyword, ...)` | `save_search_posts(keyword, output_dir, ...)` |
|
|
70
|
+
| Native cross-user query | `get_search_query(query, ...)` | `iter_search_query(query, ...)` | `save_search_query(query, output_dir, ...)` |
|
|
71
|
+
| Quotes of a post | `get_post_quotes(post_id_or_url, ...)` | `iter_post_quotes(post_id_or_url, ...)` | `save_post_quotes(post_id_or_url, output_dir, ...)` |
|
|
72
|
+
| Account repost activity | `get_user_reposts(handle, ...)` | `iter_user_reposts(handle, ...)` | `save_user_reposts(handle, output_dir, ...)` |
|
|
73
|
+
| Followers / following | `get_followers(user_id, ...)` / `get_following(user_id, ...)` | `iter_followers(user_id, ...)` / `iter_following(user_id, ...)` | `save_followers(user_id, output_dir, ...)` / `save_following(user_id, output_dir, ...)` |
|
|
74
|
+
| Reposter user list | `get_post_reposters(post_id_or_url, ...)` | `iter_post_reposters(post_id_or_url, ...)` | `save_post_reposters(post_id_or_url, output_dir, ...)` |
|
|
68
75
|
| Main posts + replies | — | — | `save_user_activity(handle, output_dir, ...)` |
|
|
69
76
|
| Multiple accounts | — | — | `save_users_activity(handles, output_dir, ...)` |
|
|
70
77
|
|
|
71
|
-
- `get_*` returns a `Profile`, `Post`, or `list[
|
|
78
|
+
- `get_*` returns a `Profile`, `Post`, `list[Post]`, or network `list[Profile]`; it does not write a dataset.
|
|
72
79
|
- `iter_*` returns an iterator. Requests happen as it is consumed.
|
|
73
|
-
- `save_*`
|
|
80
|
+
- `save_*` returns `CollectionResult` with `output_dir`, `post_count`, `pages_fetched`, `complete`, and `reason`. For post datasets `post_count` counts unique records, including replies; for network datasets this compatibility field counts observed users. Consult each manifest for table counts and coverage.
|
|
74
81
|
- Parameters: `handle` for one account, `handles` for several, `keyword` for search, `output_dir` for saved datasets, and `since`/`until` for dates. Options are keyword-only.
|
|
75
82
|
- Dates are UTC: `since` inclusive, `until` exclusive. `max_pages` and `limit` must be positive integers or `None`. Saved timeline/mirror `limit` is a page-boundary stopping threshold, so a final page can exceed it; iterator limits are exact.
|
|
76
83
|
|
|
77
|
-
See [API reference](docs/api.md) for signatures, return types and compatibility names.
|
|
84
|
+
See [API reference](https://github.com/haochengw372-hash/pyXcom/blob/main/docs/api.md) for signatures, return types and compatibility names.
|
|
78
85
|
|
|
79
86
|
### Search one account
|
|
80
87
|
|
|
@@ -92,6 +99,40 @@ with XClient(profile="Default") as client:
|
|
|
92
99
|
|
|
93
100
|
Direct account search scans main posts and authored replies, then matches a literal word/phrase without case sensitivity. Matching posts do not represent deduplicated real-world events. Cross-user search requires an explicit `mirror_base`; it sends search terms to that mirror and retrieves discovered post details from X. No mirror is enabled by default.
|
|
94
101
|
|
|
102
|
+
### Native search, quotes and repost activity
|
|
103
|
+
|
|
104
|
+
`*_search_query` submits X search syntax to the native Latest search endpoint; it does not require or use a mirror. `*_search_posts` retains its account/literal behavior above. Use a separate output directory for each query or source:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
with XClient(profile="Default") as client:
|
|
108
|
+
result = client.save_search_query(
|
|
109
|
+
'("usage reset" OR "rate limit reset") (OpenAI OR Codex)',
|
|
110
|
+
"output/reset_search", since="2025-09-29", until="2026-09-30",
|
|
111
|
+
max_pages=2, limit=100,
|
|
112
|
+
)
|
|
113
|
+
print(result.complete, result.reason)
|
|
114
|
+
client.save_post_quotes("1973931546550894681", "output/reset_quotes", max_pages=2)
|
|
115
|
+
client.save_user_reposts("thsottiaux", "output/tibo_reposts", max_pages=2)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Quote discovery uses native search followed by a check of `quoted_post_id`; zero matches do not establish zero quotes. Repost activity is collected from visible account timeline wrappers, preserving the repost ID and its action timestamp separately from the original post ID/time. A flattened timeline that only returns originals cannot establish action times and reports `repost_activity_unavailable` with an incomplete result. It is not a complete historical repost archive. Search sorting, visibility and pagination remain controlled by X.
|
|
119
|
+
|
|
120
|
+
### Follow networks and reposter lists
|
|
121
|
+
|
|
122
|
+
Resolve a handle with `get_user(handle).id`, then pass that stable numeric ID to follower/following methods:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
with XClient(profile="Default") as client:
|
|
126
|
+
user_id = client.get_user("thsottiaux").id
|
|
127
|
+
client.save_followers(user_id, "output/tibo_network", max_pages=2, snapshot_id="wave-1")
|
|
128
|
+
client.save_following(user_id, "output/tibo_network", max_pages=2, snapshot_id="wave-1")
|
|
129
|
+
client.save_post_reposters("1973931546550894681", "output/reset_reposters", max_pages=2)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Follow edges point from follower to followed account. `follow_edges.csv`, `reposters.csv`, `user_snapshots.csv` and `network_manifest.json` preserve observed relationships, user profiles and coverage per snapshot. A reposter list identifies who visibly reposted a post, but its `action_time_utc` is unknown. Use account repost activity when the wrapper supplies an action time; never substitute the original post time.
|
|
133
|
+
|
|
134
|
+
Network saves resume a recoverable unfinished snapshot. Once completed or stopped by a terminal bad cursor, calling again without `snapshot_id` creates a new observation; explicitly reusing a completed ID returns that saved snapshot. Memory/iterator methods raise endpoint, parse or rate-limit errors and retain coverage in `last_network_collection`; hitting a budget returns the observed prefix. Saves return an incomplete result and preserve checkpoints on source failures. Network and post collections require separate output directories to protect their different `users.csv` exports. Follow observations cannot establish when a relationship began or reconstruct a historical follow network. Keep collection dates in the analysis. See [examples/research_networks.py](examples/research_networks.py) for a small collection recipe; importing it does not collect data.
|
|
135
|
+
|
|
95
136
|
### Collect several accounts
|
|
96
137
|
|
|
97
138
|
```python
|
|
@@ -106,7 +147,7 @@ with XClient(profile="Default") as client:
|
|
|
106
147
|
|
|
107
148
|
Repeat to resume. `rounds=0, wait_on_rate_limit=True` keeps running and waits for rate windows when needed. `progress=callback` receives status dictionaries.
|
|
108
149
|
|
|
109
|
-
## Collect a
|
|
150
|
+
## Collect a post or reply and nested comments
|
|
110
151
|
|
|
111
152
|
```python
|
|
112
153
|
with XClient(profile="Default") as client:
|
|
@@ -126,7 +167,7 @@ pyxcom post-comments 1973931546550894681 --profile Default \
|
|
|
126
167
|
--output-dir output/tibo_conversation
|
|
127
168
|
```
|
|
128
169
|
|
|
129
|
-
The
|
|
170
|
+
The seed can be a main post or a reply. Level 1 replies directly to that seed; level 2 replies to those comments. Main seeds belong in `posts.csv`; reply seeds remain replies in `comments.csv`. `seed_post_id` and `seed_relative_depth` describe this branch; `root_post_id`, `parent_post_id` and `depth` retain actual conversation relationships and do not relabel a reply as a platform root. Ancestors returned as context are stored in `context_posts.csv`, not counted as collected descendants. `since`/`until` filter descendant timestamps; traversal retains the ancestry needed to reach in-window descendants. Quoted content, unrelated recommendations and comments beyond the requested depth are excluded. Profile details absent from the response remain unavailable.
|
|
130
171
|
|
|
131
172
|
`max_comments` is an exact total comment cap, excluding the root. `max_pages` caps conversation requests per call. Repeat the same root/depth/output to resume saved branches and cursors; raise the comment cap to continue a capped dataset. Pending page records are retained, so stopping midway through a page does not skip them. Changing depth requires a separate dataset. Python accepts `None` for unlimited comment/page budgets; depth must be positive.
|
|
132
173
|
|
|
@@ -151,12 +192,16 @@ output/tibo_year/
|
|
|
151
192
|
|
|
152
193
|
Replies to oneself remain comments. A quote within a reply retains its quote link in `comments.csv`. Direct replies are depth 1; replies to those are depth 2. Missing ancestry leaves `depth` empty with a `depth_status`; `parent_in_dataset` and `root_in_dataset` indicate observed relationships. Do not interpret missing parents as first-level replies.
|
|
153
194
|
|
|
154
|
-
`user_replies` means replies **authored by the selected account**. Use `*_post_comments` to collect replies below a particular
|
|
195
|
+
`user_replies` means replies **authored by the selected account**. Use `*_post_comments` to collect replies below a particular post or reply from all visible authors, including nested replies. Neither endpoint establishes full conversation coverage.
|
|
155
196
|
|
|
156
197
|
CSV conventions: UTF-8 with BOM, snake_case columns, string IDs (import as text in Excel), JSON arrays for list fields, empty values for unavailable scalars, UTC timestamp fields. Users without a saved profile have `profile_available=false`. Engagement counts are snapshots; unknown values are not replaced with zero.
|
|
157
198
|
|
|
158
199
|
If source records include pure reposts, an additional `interactions.csv` preserves them. The current authored-account collector does not establish complete repost activity. `manifest.json` records schema/layout versions, table counts, collection scope, relationship coverage and hashes. `.pyxcom/` must be retained for resuming; the three CSV files can be shared independently for analysis.
|
|
159
200
|
|
|
201
|
+
Saved collection pages also retain platform response JSON under `.pyxcom/raw/` (network pages under their snapshot state) and query provenance in `.pyxcom/collection_log.jsonl`. Repeated post observations keep returned engagement metrics in `metric_snapshots.csv`, with source observations in `.pyxcom/observations.jsonl` and metric history in `.pyxcom/metric_snapshots.jsonl`. Unreturned counts remain unavailable. These are observations at retrieval time, not historical metrics at publication. Credentials are not included in the response archive.
|
|
202
|
+
|
|
203
|
+
When source records contain reply, quote or repost relationships, `post_edges.csv` exports their directions, source/target post and user IDs, action and observation times, and target availability/resolution flags. An unavailable target is retained; its user ID stays empty if unresolved. Known target authors can appear in `users.csv` as stubs with `profile_available=false`. Context-only records do not create source edges. Relationship, context and metric exports appear when observations provide the corresponding data.
|
|
204
|
+
|
|
160
205
|
## Command line
|
|
161
206
|
|
|
162
207
|
CLI task names correspond to the Python methods:
|
|
@@ -171,10 +216,19 @@ pyxcom search-posts reset --handle thsottiaux --profile Default \
|
|
|
171
216
|
--since 2025-09-29 --until 2026-09-30 --output-dir output/tibo_reset
|
|
172
217
|
pyxcom users-activity --handles OpenAI AnthropicAI --profile Default \
|
|
173
218
|
--since 2025-09-29 --until 2026-09-30 --output-dir output/ai_year
|
|
219
|
+
pyxcom search-query '("usage reset" OR "rate limit reset") Codex' --profile Default \
|
|
220
|
+
--since 2025-09-29 --until 2026-09-30 --max-pages 2 --output-dir output/reset_search
|
|
221
|
+
pyxcom post-quotes 1973931546550894681 --max-pages 2 --output-dir output/reset_quotes
|
|
222
|
+
pyxcom user-reposts thsottiaux --max-pages 2 --output-dir output/tibo_reposts
|
|
223
|
+
pyxcom user-followers USER_ID --max-pages 2 --snapshot-id wave-1 --output-dir output/network
|
|
224
|
+
pyxcom user-following USER_ID --max-pages 2 --snapshot-id wave-1 --output-dir output/network
|
|
225
|
+
pyxcom post-reposters 1973931546550894681 --max-pages 2 --output-dir output/reposters
|
|
174
226
|
```
|
|
175
227
|
|
|
176
228
|
Add `--proxy URL` if your network requires one. Use `pyxcom COMMAND --help` for task options. Individual `user`, `post`, and `raw-post` commands accept `--output FILE` for a JSON file.
|
|
177
229
|
|
|
230
|
+
`post-comments --max-comments all --max-pages all` removes comment/page caps; `--max-depth` still requires a positive integer (default 2). New discovery/network commands also accept `all` for `--max-pages` and `--limit`. Unlimited budgets do not remove visibility or rate limits.
|
|
231
|
+
|
|
178
232
|
## Export, migration and verification
|
|
179
233
|
|
|
180
234
|
These operations work offline, without browser credentials:
|
|
@@ -208,17 +262,17 @@ X endpoints may change or impose limits. pyXcom discovers current GraphQL query
|
|
|
208
262
|
|
|
209
263
|
## License
|
|
210
264
|
|
|
211
|
-
pyXcom is released under the [MIT License](LICENSE). Copyright © 2026 Haocheng Wang.
|
|
265
|
+
pyXcom is released under the [MIT License](https://github.com/haochengw372-hash/pyXcom/blob/main/LICENSE). Copyright © 2026 Haocheng Wang.
|
|
212
266
|
|
|
213
267
|
## Citation
|
|
214
268
|
|
|
215
269
|
**Author:** Haocheng Wang, Communication University of China.
|
|
216
270
|
|
|
217
|
-
If you use pyXcom in a paper, thesis, dataset, or other research output, please cite the software and report the version used. GitHub's **Cite this repository** menu reads the machine-readable [CITATION.cff](CITATION.cff).
|
|
271
|
+
If you use pyXcom in a paper, thesis, dataset, or other research output, please cite the software and report the version used. GitHub's **Cite this repository** menu reads the machine-readable [CITATION.cff](https://github.com/haochengw372-hash/pyXcom/blob/main/CITATION.cff).
|
|
218
272
|
|
|
219
273
|
**Suggested reference**
|
|
220
274
|
|
|
221
|
-
Wang, H. (2026). *pyXcom: Structured and auditable X data collection for communication research* (Version 0.
|
|
275
|
+
Wang, H. (2026). *pyXcom: Structured and auditable X data collection for communication research* (Version 0.7.0) [Computer software]. https://github.com/haochengw372-hash/pyXcom
|
|
222
276
|
|
|
223
277
|
**BibTeX**
|
|
224
278
|
|
|
@@ -227,9 +281,15 @@ Wang, H. (2026). *pyXcom: Structured and auditable X data collection for communi
|
|
|
227
281
|
author = {Wang, Haocheng},
|
|
228
282
|
title = {{pyXcom}: Structured and Auditable X Data Collection for Communication Research},
|
|
229
283
|
year = {2026},
|
|
230
|
-
version = {0.
|
|
284
|
+
version = {0.7.0},
|
|
231
285
|
url = {https://github.com/haochengw372-hash/pyXcom}
|
|
232
286
|
}
|
|
233
287
|
```
|
|
234
288
|
|
|
235
289
|
For reproducible reporting, also describe the collection dates, account or keyword scope, comment-depth limits, package version, and coverage/stop reasons recorded in the output manifest. No DOI or published-paper citation is currently assigned; this reference cites the software itself. Citation is appreciated and does not add a condition to the MIT license.
|
|
290
|
+
|
|
291
|
+
For longitudinal profile comparisons use `user_snapshots.csv`; `users.csv` consolidates known profile fields. Canonical post tables keep the first saved observation for each ID; later returned text and metrics remain in observation archives and metric snapshots.
|
|
292
|
+
|
|
293
|
+
Offline development checks: `PYTHONPATH=src python -m pytest -q` and `ruff check src tests examples`. The source distribution includes tests; testing tools are development-only, not runtime dependencies.
|
|
294
|
+
|
|
295
|
+
Live validation: current X SearchTimeline/Followers use POST read queries; Retweeters is discovered from public lazy-loaded TweetActivity assets. If a search source returns posts outside explicit dates, they are excluded locally and `manifest.json.date_scope` records the mismatch. An empty bounded result with this warning does not establish historical absence. Native reposts generate only repost edges; embedded quote/reply context is not attributed as another action by the reposter.
|
pyxcom-0.7.0/docs/api.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Public API — pyXcom 0.7.0
|
|
2
|
+
|
|
3
|
+
The native discovery and network methods have passed offline tests and bounded authenticated live endpoint tests. These tests verify the exercised acquisition paths, not complete historical or network coverage.
|
|
4
|
+
|
|
5
|
+
Import `XClient`, `Profile`, `Post`, `CollectionResult`, `PyXcomError`, `AuthenticationError`, `APIError`, and `RateLimitError` from `pyxcom`.
|
|
6
|
+
|
|
7
|
+
## Client
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
XClient(*, browser="chrome", profile=None, cookie_db=None, proxy=None,
|
|
11
|
+
mirror_base=None, delay=1.0, timeout=30)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Use as a context manager to close connections. `profile` selects the local browser profile; it is not an X username. Manually sign in first. The client never launches a browser.
|
|
15
|
+
|
|
16
|
+
## Lookup methods
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
get_user(handle: str) -> Profile
|
|
20
|
+
get_post(post_id_or_url: str) -> Post
|
|
21
|
+
get_posts(post_ids: list[str]) -> list[Post]
|
|
22
|
+
get_raw_post(post_id_or_url: str) -> dict
|
|
23
|
+
get_raw_user_timeline(handle: str, *, timeline="posts", cursor=None) -> dict
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`get_posts` can return fewer items when X does not return every requested ID. Raw methods return the platform response for advanced use and have no stable field schema.
|
|
27
|
+
|
|
28
|
+
## Account content
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
iter_user_posts(handle, *, timeline="posts", since=None, until=None,
|
|
32
|
+
max_pages=None, limit=None) -> Iterator[Post]
|
|
33
|
+
get_user_posts(handle, *, timeline="posts", since=None, until=None,
|
|
34
|
+
max_pages=None, limit=None) -> list[Post]
|
|
35
|
+
save_user_posts(handle, output_dir, *, timeline="posts", since=None, until=None,
|
|
36
|
+
max_pages=None, limit=None) -> CollectionResult
|
|
37
|
+
|
|
38
|
+
iter_user_replies(handle, *, since=None, until=None,
|
|
39
|
+
max_pages=None, limit=None) -> Iterator[Post]
|
|
40
|
+
get_user_replies(handle, *, since=None, until=None,
|
|
41
|
+
max_pages=None, limit=None) -> list[Post]
|
|
42
|
+
save_user_replies(handle, output_dir, *, since=None, until=None,
|
|
43
|
+
max_pages=None, limit=None) -> CollectionResult
|
|
44
|
+
|
|
45
|
+
save_user_activity(handle, output_dir, *, since=None, until=None,
|
|
46
|
+
max_pages=None) -> CollectionResult
|
|
47
|
+
save_users_activity(handles, output_dir, *, since, until, pages_per_round=5,
|
|
48
|
+
rounds=1, wait_on_rate_limit=False, progress=None) -> CollectionResult
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`handle: str`; `handles: list[str]`; `output_dir: str | Path`. Dates are `str | None` in YYYY-MM-DD UTC. `max_pages` and `limit` are positive `int | None`. `progress` is an optional callable taking a dictionary. `get`/`iter` start a fresh traversal; `save` resumes the same dataset query. `limit` on a saved timeline/mirror search is checked after a page; it may overshoot. Account activity and multi-account saves intentionally have no total-record `limit`.
|
|
52
|
+
|
|
53
|
+
`user_posts` defaults to original/quote main posts. `user_replies` returns authored replies, including self-replies. Neither is an API to retrieve a main post's entire received comment tree. `timeline` is a compatibility option for selecting the source stream.
|
|
54
|
+
|
|
55
|
+
## Comments received by a post or reply
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
iter_post_comments(post_id_or_url: str, *, max_depth: int = 2,
|
|
59
|
+
max_comments: int | None = 100,
|
|
60
|
+
max_pages: int | None = 20,
|
|
61
|
+
since: str | None = None, until: str | None = None) -> Iterator[Post]
|
|
62
|
+
get_post_comments(post_id_or_url: str, *, max_depth: int = 2,
|
|
63
|
+
max_comments: int | None = 100,
|
|
64
|
+
max_pages: int | None = 20,
|
|
65
|
+
since: str | None = None, until: str | None = None) -> list[Post]
|
|
66
|
+
save_post_comments(post_id_or_url: str, output_dir: str | Path, *,
|
|
67
|
+
max_depth: int = 2, max_comments: int | None = 100,
|
|
68
|
+
max_pages: int | None = 20,
|
|
69
|
+
since: str | None = None, until: str | None = None) -> CollectionResult
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
These methods expand descendants of the seed from all visible authors, including when the seed itself is a reply. They differ from `*_user_replies`, which fetch only one account's authored replies. `get` and `iter` return descendants only; `save` preserves the seed in its proper main/comment table, profiles in `users.csv`, comments in `comments.csv`, and private traversal state for resume. Ancestor context is retained in `context_posts.csv` and is not counted as descendants.
|
|
73
|
+
|
|
74
|
+
Traversal depth is relative to the seed (direct replies=1), recorded as `seed_post_id` and `seed_relative_depth`. Actual platform `root_post_id`, `parent_post_id` and `depth` remain separate; missing ancestors can leave actual `depth` unresolved. UTC `since`/`until` filter returned descendants while preserving traversal context. Comment cap is exact and cumulative per dataset; page cap applies to each call. A rate-limited save preserves its queue with `complete=false`; memory/iterator methods raise `RateLimitError`. Network/parse failures save a partial checkpoint and raise. Repeating a save with larger comment/page budgets resumes it; changing seed/depth/date scope requires a separate dataset. Completion only means visible traversal within the requested depth ended, not an exhaustive historical thread. CLI comment/page caps accept `all` for Python `None`; depth must remain a positive integer.
|
|
75
|
+
|
|
76
|
+
## Keyword search
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
iter_search_posts(keyword, *, handle=None, since=None, until=None,
|
|
80
|
+
max_pages=None, limit=None) -> Iterator[Post]
|
|
81
|
+
get_search_posts(keyword, *, handle=None, since=None, until=None,
|
|
82
|
+
max_pages=None, limit=None) -> list[Post]
|
|
83
|
+
save_search_posts(keyword, output_dir, *, handle=None, since=None, until=None,
|
|
84
|
+
max_pages=None, limit=None) -> CollectionResult
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`keyword: str`, `handle: str | None`. Direct search requires a handle and matches literal text without case sensitivity across that account's main posts and replies. Cross-account discovery requires constructor `mirror_base`. `max_pages` applies per stream for direct search.
|
|
88
|
+
|
|
89
|
+
## Native discovery
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
iter_search_query(query, *, since=None, until=None, max_pages=None, limit=None) -> Iterator[Post]
|
|
93
|
+
get_search_query(query, *, since=None, until=None, max_pages=None, limit=None) -> list[Post]
|
|
94
|
+
save_search_query(query, output_dir, *, since=None, until=None,
|
|
95
|
+
max_pages=None, limit=None) -> CollectionResult
|
|
96
|
+
|
|
97
|
+
iter_post_quotes(post_id_or_url, *, since=None, until=None,
|
|
98
|
+
max_pages=None, limit=None) -> Iterator[Post]
|
|
99
|
+
get_post_quotes(post_id_or_url, *, since=None, until=None,
|
|
100
|
+
max_pages=None, limit=None) -> list[Post]
|
|
101
|
+
save_post_quotes(post_id_or_url, output_dir, *, since=None, until=None,
|
|
102
|
+
max_pages=None, limit=None) -> CollectionResult
|
|
103
|
+
|
|
104
|
+
iter_user_reposts(handle, *, since=None, until=None,
|
|
105
|
+
max_pages=None, limit=None) -> Iterator[Post]
|
|
106
|
+
get_user_reposts(handle, *, since=None, until=None,
|
|
107
|
+
max_pages=None, limit=None) -> list[Post]
|
|
108
|
+
save_user_reposts(handle, output_dir, *, since=None, until=None,
|
|
109
|
+
max_pages=None, limit=None) -> CollectionResult
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
All options are explicit and keyword-only. Dates are inclusive/exclusive UTC dates; budgets are positive integers or `None`. Native search uses X Latest, preserves query operators, and does not use the mirror. `*_post_quotes` discovers candidates using `quoted_tweet_id:` and verifies the returned target ID. Its result is a visible search sample, not the complete quote population. `*_user_reposts` parses visible repost wrappers in the account timeline, preserving the action ID/time and original target ID/time; it cannot infer missing reposts. If X returns flattened originals instead of wrappers, action times cannot be recovered; the save reports incomplete `repost_activity_unavailable` rather than substituting original publication times.
|
|
113
|
+
|
|
114
|
+
Saved discovery queries retain original/effective query provenance, raw response pages and resumable cursors. Save scopes cannot be changed within the same directory. Use `complete` and `reason` to report caps, source end, rate limits or stalled pagination. A visible source end does not establish historical exhaustiveness.
|
|
115
|
+
|
|
116
|
+
## Network lists and snapshots
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
iter_followers(user_id, *, max_pages=None, limit=None) -> Iterator[Profile]
|
|
120
|
+
get_followers(user_id, *, max_pages=None, limit=None) -> list[Profile]
|
|
121
|
+
save_followers(user_id, output_dir, *, max_pages=None, limit=None,
|
|
122
|
+
snapshot_id=None) -> CollectionResult
|
|
123
|
+
|
|
124
|
+
iter_following(user_id, *, max_pages=None, limit=None) -> Iterator[Profile]
|
|
125
|
+
get_following(user_id, *, max_pages=None, limit=None) -> list[Profile]
|
|
126
|
+
save_following(user_id, output_dir, *, max_pages=None, limit=None,
|
|
127
|
+
snapshot_id=None) -> CollectionResult
|
|
128
|
+
|
|
129
|
+
iter_post_reposters(post_id_or_url, *, max_pages=None, limit=None) -> Iterator[Profile]
|
|
130
|
+
get_post_reposters(post_id_or_url, *, max_pages=None, limit=None) -> list[Profile]
|
|
131
|
+
save_post_reposters(post_id_or_url, output_dir, *, max_pages=None, limit=None,
|
|
132
|
+
snapshot_id=None) -> CollectionResult
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`user_id` is a numeric ID string, resolved with `get_user(handle).id`. Network methods have no historical date filter. Saves write `follow_edges.csv` or `reposters.csv`, `users.csv`, `user_snapshots.csv`, and `network_manifest.json`. Network and post collections must use separate output directories to protect their different table layouts. `CollectionResult.post_count` is the observed user count for these methods. Follow edges point follower → followed account. Reposter edges point reposter → original author with `target_post_id`; `action_time_utc` remains unknown because the user list does not return repost activity times. Discovery/network CLI budgets accept `all` for Python `None`.
|
|
136
|
+
|
|
137
|
+
`snapshot_id` accepts 1–128 letters, digits, underscores or hyphens. Without it, a save resumes a recoverable unfinished snapshot for that source, or creates a new snapshot after completion or a terminal bad cursor. Reusing an explicit completed ID returns that observation. History and raw pages remain under `.pyxcom/networks/`; the network manifest records completion and stop reasons per snapshot. `get`/`iter` raise `APIError`, `ParseError` or `RateLimitError` on source failures and set `last_network_collection`; budget stops return the prefix. Saves return incomplete status with resumable checkpoints for recoverable failures. Snapshot time is the time of observation, not relationship creation time. Present-day follower/following lists cannot backfill historical follow networks.
|
|
138
|
+
|
|
139
|
+
## Observation archives
|
|
140
|
+
|
|
141
|
+
Saved post collections retain page responses under `.pyxcom/raw/` and collection provenance under `.pyxcom/collection_log.jsonl`; network responses are kept under each snapshot state. Re-observing a post preserves returned engagement-count history in `metric_snapshots.csv` and `.pyxcom/metric_snapshots.jsonl`, with observations retained in `.pyxcom/observations.jsonl`, while public post tables remain deduplicated by ID. Unknown counts remain unavailable, not zero. A repeated observation does not reconstruct an earlier historical metric. Raw archives contain platform payloads, not request cookies or authorization headers.
|
|
142
|
+
|
|
143
|
+
`post_edges.csv` exports observed reply, quote and repost edges when present. Columns are `source_post_id`, `target_post_id`, `source_user_id`, `target_user_id`, `edge_type`, `action_time_utc`, `observed_at_utc`, `target_post_available`, `target_author_resolved` and `observation_role`. Edges point from the acting user/post to the target. Unavailable targets remain in the table; unresolved target user IDs stay empty with `target_author_resolved=false`. Known but otherwise unobserved target authors have stub user rows (`profile_available=false`). Context-only records do not generate source edges. Relationship, context and metric exports are conditional on corresponding observations being present.
|
|
144
|
+
|
|
145
|
+
## Offline dataset functions
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
export_tables(output_dir: str | Path) -> dict
|
|
149
|
+
validate_tables(output_dir: str | Path) -> dict
|
|
150
|
+
validate_collection(output_dir: str | Path) -> dict
|
|
151
|
+
validate_network_collection(output_dir: str | Path) -> dict
|
|
152
|
+
finalize_collection(output_dir: str | Path) -> CollectionResult
|
|
153
|
+
schema_summary(output_dir: str | Path) -> dict
|
|
154
|
+
write_schema_report(output_dir: str | Path) -> dict
|
|
155
|
+
apply_role_schema(output_dir: str | Path) -> dict
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Export/finalization migrate legacy files with backups and regenerate public tables. Validation never fetches X data. `validate_collection` checks internal observations and public tables; `validate_tables` checks normalized relationships, source/file hashes and counts. Reports describe missing ancestry explicitly. `schema_summary` describes the internal standard Post/Profile fields, not only the normalized table columns.
|
|
159
|
+
|
|
160
|
+
Post export/finalization/schema helpers operate on post datasets. `validate_collection` automatically recognizes network datasets; `validate_network_collection` explicitly verifies network snapshot hashes, directions and provenance. To resume or regenerate network exports, repeat the corresponding network `save_*` call.
|
|
161
|
+
|
|
162
|
+
## Backward-compatible names
|
|
163
|
+
|
|
164
|
+
| Previous API | Preferred API |
|
|
165
|
+
| --- | --- |
|
|
166
|
+
| `iter_search(..., user=...)` | `iter_search_posts(..., handle=...)` |
|
|
167
|
+
| `get_search(..., user=...)` | `get_search_posts(..., handle=...)` |
|
|
168
|
+
| `save_search(..., user=...)` | `save_search_posts(..., handle=...)` |
|
|
169
|
+
| `save_accounts(...)` | `save_users_activity(...)` |
|
|
170
|
+
| `*_user_posts(..., timeline="replies")` | `*_user_replies(...)` |
|
|
171
|
+
|
|
172
|
+
No removal is scheduled. These names call the same implementation; no separate authentication flow or duplicate scraper is introduced.
|
|
@@ -36,18 +36,18 @@ production releases.
|
|
|
36
36
|
`twine check`, installs the wheel into a clean virtual environment, runs the
|
|
37
37
|
unit tests, and checks the CLI. The separate publish job downloads those exact
|
|
38
38
|
artifacts and uploads them with an OIDC identity.
|
|
39
|
-
4. Install the TestPyPI release in a new environment. For version `0.
|
|
39
|
+
4. Install the TestPyPI release in a new environment. For version `0.7.0`:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
python3 -m venv /tmp/pyxcom-testpypi-
|
|
43
|
-
/tmp/pyxcom-testpypi-
|
|
42
|
+
python3 -m venv /tmp/pyxcom-testpypi-070
|
|
43
|
+
/tmp/pyxcom-testpypi-070/bin/python -m pip install \
|
|
44
44
|
'browser-cookie3>=0.19,<1' 'beautifulsoup4>=4.12,<5' 'httpx>=0.27,<1'
|
|
45
|
-
/tmp/pyxcom-testpypi-
|
|
46
|
-
--index-url https://test.pypi.org/simple/ --no-deps 'pyXcom==0.
|
|
47
|
-
/tmp/pyxcom-testpypi-
|
|
48
|
-
/tmp/pyxcom-testpypi-
|
|
45
|
+
/tmp/pyxcom-testpypi-070/bin/python -m pip install \
|
|
46
|
+
--index-url https://test.pypi.org/simple/ --no-deps 'pyXcom==0.7.0'
|
|
47
|
+
/tmp/pyxcom-testpypi-070/bin/python -m pip check
|
|
48
|
+
/tmp/pyxcom-testpypi-070/bin/python -c \
|
|
49
49
|
'from importlib.metadata import version; from pyxcom import XClient; print(version("pyXcom"))'
|
|
50
|
-
/tmp/pyxcom-testpypi-
|
|
50
|
+
/tmp/pyxcom-testpypi-070/bin/pyxcom --help
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Dependencies come from the normal PyPI index, while the package under test
|
|
@@ -65,11 +65,11 @@ After the production jobs succeed, verify the public index in another new virtua
|
|
|
65
65
|
environment:
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
python3 -m venv /tmp/pyxcom-pypi-
|
|
69
|
-
/tmp/pyxcom-pypi-
|
|
70
|
-
/tmp/pyxcom-pypi-
|
|
71
|
-
/tmp/pyxcom-pypi-
|
|
72
|
-
/tmp/pyxcom-pypi-
|
|
68
|
+
python3 -m venv /tmp/pyxcom-pypi-070
|
|
69
|
+
/tmp/pyxcom-pypi-070/bin/python -m pip install --index-url https://pypi.org/simple/ 'pyXcom==0.7.0'
|
|
70
|
+
/tmp/pyxcom-pypi-070/bin/python -m pip check
|
|
71
|
+
/tmp/pyxcom-pypi-070/bin/python -c 'from importlib.metadata import version; from pyxcom import XClient; print(version("pyXcom"))'
|
|
72
|
+
/tmp/pyxcom-pypi-070/bin/pyxcom --help
|
|
73
73
|
```
|
|
74
74
|
|
|
75
75
|
The workflow is manually dispatched; pull requests and branch pushes do not
|