{"id":51596619,"url":"https://github.com/MEOMcGill/pytok","last_synced_at":"2026-07-11T19:01:20.431Z","repository":{"id":124242718,"uuid":"555492190","full_name":"MEOMcGill/pytok","owner":"MEOMcGill","description":"A web scraper for TikTok","archived":false,"fork":false,"pushed_at":"2026-07-03T18:09:23.000Z","size":376,"stargazers_count":157,"open_issues_count":4,"forks_count":22,"subscribers_count":9,"default_branch":"master","last_synced_at":"2026-07-03T19:11:20.258Z","etag":null,"topics":["data-collection","tiktok","tiktok-api","tiktok-scraper","web-scraper"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MEOMcGill.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-10-21T17:32:46.000Z","updated_at":"2026-07-03T18:09:27.000Z","dependencies_parsed_at":null,"dependency_job_id":"10f1e47e-8480-44e3-91ec-5074e9485b8b","html_url":"https://github.com/MEOMcGill/pytok","commit_stats":null,"previous_names":["meomcgill/pytok"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/MEOMcGill/pytok","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MEOMcGill%2Fpytok","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MEOMcGill%2Fpytok/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MEOMcGill%2Fpytok/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MEOMcGill%2Fpytok/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MEOMcGill","download_url":"https://codeload.github.com/MEOMcGill/pytok/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MEOMcGill%2Fpytok/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35372639,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-11T02:00:05.354Z","response_time":104,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["data-collection","tiktok","tiktok-api","tiktok-scraper","web-scraper"],"created_at":"2026-07-11T19:01:17.044Z","updated_at":"2026-07-11T19:01:20.422Z","avatar_url":"https://github.com/MEOMcGill.png","language":"Python","funding_links":[],"categories":["Social Media Crawlers"],"sub_categories":[],"readme":"\n[![DOI](https://zenodo.org/badge/555492190.svg)](https://zenodo.org/doi/10.5281/zenodo.12802713)\n\n# pytok\n\nThis is a zendriver based version of David Teacher's unofficial api wrapper for TikTok.com in python. It re-implements a currently limited set of the features of the original library, with a shifted focus on using browser automation to allow automatic captcha solves with a hopefully minor trade-off in performance.\n\n## Installation\n\n```bash\npip install git+https://github.com/networkdynamics/pytok.git@master\n```\n\n## Quick Start Guide\n\nHere's a quick bit of code to get the videos from a particular user on TikTok. There's more examples in the [examples](https://github.com/networkdynamics/pytok/tree/master/examples) directory.\n\n```py\nimport asyncio\n\nfrom pytok.tiktok import PyTok\n\nasync def main():\n    async with PyTok() as api:\n        user = api.user(username=\"therock\")\n        user_data = await user.info()\n        print(user_data)\n\n        videos = []\n        async for video in user.videos():\n            video_data = await video.info()\n            print(video_data)\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n\nPlease note pulling data from TikTok takes a while! We recommend leaving the scripts running on a server for a while for them to finish downloading everything. Feel free to play around with the delay constants to either speed up the process or avoid TikTok rate limiting, like so: `PyTok(request_delay=10)`\n\n## Accounts, login, and persistent sessions\n\nPyTok supports scraping as a logged-in account, and managing multiple accounts, via an **accounts pool**: a SQLite-backed set of TikTok accounts, each with its own persistent Chrome profile and a cookie/identity backup. You register an account and log in **once** (interactively), and every session afterwards comes up already authenticated from that profile, repairing itself from the cookie backup if the profile's session is lost.\n\nThe pool lives in `~/.pytok` by default (override with the `$PYTOK_HOME` env var). The database holds credentials and cookie backups in plaintext, so keep that directory private — it is deliberately kept outside the repo.\n\nRegister an account and log in once with the CLI:\n\n```bash\n# Add the account (credentials are stored in ~/.pytok/accounts.db)\npython -m pytok.accounts.cli add --username you@email.com --password 'your-password'\n\n# Open a browser and log in. Complete any email/SMS/captcha verification in the\n# window; on success PyTok captures the account identity and a cookie backup.\npython -m pytok.accounts.cli login --username you@email.com\n\n# Inspect the pool\npython -m pytok.accounts.cli list -v\n```\n\nThen scrape as a logged-in account with `PyTok.from_pool`, which acquires an available account (or a specific one via `username=`) already signed in:\n\n```py\nimport asyncio\n\nfrom pytok.tiktok import PyTok\nfrom pytok.accounts import AccountsPool\n\nasync def main():\n    pool = AccountsPool()\n    async with await PyTok.from_pool(pool) as api:\n        hashtag = api.hashtag(name=\"fyp\")\n        async for video in hashtag.videos(count=100):\n            print(await video.info())\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\nOther useful CLI commands: `info \u003cusername\u003e`, `stats`, `activate`/`deactivate`, `release` (recover an account left in-use by a crashed run), `unlock`, and `delete`. Run `python -m pytok.accounts.cli --help` for the full list.\n\n## Scraping concurrently across accounts\n\n`WorkerPool` runs many sessions at once — each worker owns one account and its own isolated Chrome profile, so N accounts means N concurrent scrapers. Tasks are plain async callables `async def task(api) -\u003e result` distributed across a shared queue:\n\n```py\nimport asyncio\n\nfrom pytok.accounts import AccountsPool, WorkerPool\n\nasync def scrape_user(api, handle):\n    videos = []\n    async for video in api.user(username=handle).videos(count=100):\n        videos.append(await video.info())\n    return handle, videos\n\nasync def main():\n    pool = AccountsPool()\n    async with WorkerPool(pool, max_workers=3) as wp:\n        results = await wp.run([\n            lambda api, h=h: scrape_user(api, h)\n            for h in [\"therock\", \"khaby.lame\", \"charlidamelio\"]\n        ])\n    for handle, videos in results:\n        print(f\"@{handle}: {len(videos)} videos\")\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n`max_workers` is capped to the number of active accounts. Workers rotate/rest accounts and rebuild crashed sessions automatically. See [`examples/worker_pool_example.py`](https://github.com/networkdynamics/pytok/tree/master/examples/worker_pool_example.py).\n\nPlease do not hesitate to make an issue in this repo to get our help with this!\n\n## Citation\n\nIf you use this library in your research, please cite it using the following BibTeX entry:\n\n```bibtex\n@article{steel2023invasion,\n  title={The invasion of ukraine viewed through tiktok: A dataset},\n  author={Steel, Benjamin and Parker, Sara and Ruths, Derek},\n  journal={arXiv preprint arXiv:2301.08305},\n  year={2023}\n}\n\n```\n\n## Format and Schema\n\nThe JSONable dictionary returned by the `info()` methods contains all of the data that the TikTok API returns. We have provided helper functions to parse that data into Pandas DataFrames, `utils.get_comment_df()`, `utils.get_video_df()` and `utils.get_user_df()` for the data from comments, videos, and users respectively.\n\nThe video dataframe will contain the following columns:\n|Field name | Description |\n|----------|----------|\n|`video_id`| Unique video ID |\n|`createtime`| UTC datetime of video creation time in YYYY-MM-DD HH:MM:SS format |\n|`author_name`| Unique author name |\n|`author_id`| Unique author ID |\n|`desc`| The full video description from the author |\n|`hashtags`| A list of hashtags used in the video description |\n|`share_video_id`| If the video is sharing another video, this is the video ID of that original video, else empty |\n|`share_video_user_id`| If the video is sharing another video, this the user ID of the author of that video, else empty |\n|`share_video_user_name`| If the video is sharing another video, this is the user name of the author of that video, else empty |\n|`share_type`| If the video is sharing another video, this is the type of the share, stitch, duet etc. |\n|`mentions`| A list of users mentioned in the video description, if any |\n|`digg_count`| The number of likes on the video |\n|`share_count`| The number of times the video was shared |\n|`comment_count`| The number of comments on the video |\n|`play_count`| The number of times the video was played |\n\nThe comment dataframe will contain the following columns:\n|Field name | Description |\n|----------|-----------|\n|`comment_id`| Unique comment ID |\n|`createtime`| UTC datetime of comment creation time in YYYY-MM-DD HH:MM:SS format |\n|`author_name`| Unique author name |\n|`author_id`| Unique author ID |\n|`text`| Text of the comment |\n|`mentions`| A list of users that are tagged in the comment |\n|`video_id`| The ID of the video the comment is on |\n|`comment_language`| The language of the comment, as predicted by the TikTok API |\n|`digg_count`| The number of likes the comment got |\n|`reply_comment_id`| If the comment is replying to another comment, this is the ID of that comment |\n\nThe user dataframe will contain the following columns:\n|Field name | Description |\n|----------|-----------|\n|`id`| Unique author ID |\n|`unique_id`| Unique user name |\n|`nickname`| Display user name, changeable |\n|`signature`| Short user description |\n|`verified`| Whether or not the user is verified |\n|`num_following`| How many other accounts the user is following |\n|`num_followers`| How many followers the user has |\n|`num_videos`| How many videos the user has made |\n|`num_likes`| How many total likes the user has had |\n|`createtime`| When the user account was made. This is derived from the `id` field, and can occasionally be incorrect with a very low unix epoch such as 1971 |\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMEOMcGill%2Fpytok","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMEOMcGill%2Fpytok","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMEOMcGill%2Fpytok/lists"}