1from apify import Actor, ApifyClient
2import asyncio
3import httpx
4import json
5import re
6
7async def main():
8 async with Actor() as actor:
9 input_data = await actor.get_input() or {}
10 urls = input_data.get("urls", [])
11 hashtags = input_data.get("hashtags", [])
12 mode = input_data.get("mode", "profiles")
13
14 results = []
15 headers = {
16 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
17 "Accept": "application/json, text/plain, */*",
18 "Referer": "https://www.tiktok.com/",
19 }
20
21 async with httpx.AsyncClient(timeout=30, follow_redirects=True) as client:
22 if mode == "profiles" and urls:
23 for url in urls:
24 username = url.rstrip("/").split("/")[-1]
25 Actor.log.info(f"Scraping TikTok profile: {username}")
26 try:
27 resp = await client.get(
28 f"https://www.tiktok.com/@{username}",
29 headers=headers
30 )
31
32 data = {"username": username, "url": url}
33
34
35 next_data_match = re.search(
36 r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>',
37 resp.text
38 )
39 if next_data_match:
40 try:
41 nd = json.loads(next_data_match.group(1))
42 user = (nd.get("props", {})
43 .get("pageProps", {})
44 .get("userInfo", {})
45 .get("user", {}))
46 stats = user.get("stats", [])
47
48 data.update({
49 "full_name": user.get("nickname", ""),
50 "bio": user.get("signature", ""),
51 "followers": user.get("followerCount", 0),
52 "following": user.get("followingCount", 0),
53 "likes": user.get("heartCount", 0),
54 "video_count": user.get("videoCount", 0),
55 "is_verified": user.get("verified", False),
56 "unique_id": user.get("uniqueId", ""),
57 "avatar_url": user.get("avatarMedium", ""),
58 "sec_uid": user.get("secUid", ""),
59 })
60 except Exception as e:
61 Actor.log.warning(f"Parse error: {e}")
62
63 results.append(data)
64 await actor.push_data(data)
65 Actor.log.info(f" Scraped: {data.get('full_name', username)} - {data.get('followers', 'N/A')} followers")
66
67 except Exception as e:
68 Actor.log.error(f"Error scraping {username}: {e}")
69 await actor.push_data({"username": username, "error": str(e), "status": "failed"})
70
71 await asyncio.sleep(2)
72
73 elif mode == "hashtags" and hashtags:
74 for tag in hashtags:
75 Actor.log.info(f"Scraping hashtag: #{tag}")
76 try:
77 resp = await client.get(
78 f"https://www.tiktok.com/tag/{tag}",
79 headers=headers
80 )
81
82 data = {"hashtag": tag, "url": f"https://www.tiktok.com/tag/{tag}"}
83
84 next_data_match = re.search(
85 r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>',
86 resp.text
87 )
88 if next_data_match:
89 try:
90 nd = json.loads(next_data_match.group(1))
91 challenge = (nd.get("props", {})
92 .get("pageProps", {})
93 .get("challengeInfo", {}))
94 data.update({
95 "views": challenge.get("viewCount", 0),
96 "posts": challenge.get("postCount", 0),
97 "description": challenge.get("desc", ""),
98 "challenge_name": challenge.get("challengeName", ""),
99 })
100 except:
101 pass
102
103 results.append(data)
104 await actor.push_data(data)
105 Actor.log.info(f" #{tag}: {data.get('views', 'N/A')} views")
106
107 except Exception as e:
108 Actor.log.error(f"Error scraping #{tag}: {e}")
109 await actor.push_data({"hashtag": tag, "error": str(e)})
110
111 await asyncio.sleep(2)
112
113 elif mode == "videos" and urls:
114 for url in urls:
115 Actor.log.info(f"Scraping video: {url}")
116 try:
117 resp = await client.get(url, headers=headers)
118 data = {"url": url}
119
120
121 next_data_match = re.search(
122 r'<script id="__NEXT_DATA__"[^>]*>(.*?)</script>',
123 resp.text
124 )
125 if next_data_match:
126 try:
127 nd = json.loads(next_data_match.group(1))
128 item_info = (nd.get("props", {})
129 .get("pageProps", {})
130 .get("itemInfo", {})
131 .get("itemStruct", {}))
132 stats = item_info.get("stats", {})
133 author = item_info.get("author", {})
134 video = item_info.get("video", {})
135
136 data.update({
137 "id": item_info.get("id", ""),
138 "description": item_info.get("desc", ""),
139 "author": author.get("nickname", ""),
140 "author_username": author.get("uniqueId", ""),
141 "plays": stats.get("playCount", 0),
142 "likes": stats.get("diggCount", 0),
143 "comments": stats.get("commentCount", 0),
144 "shares": stats.get("shareCount", 0),
145 "duration": video.get("duration", 0),
146 "cover_url": video.get("cover", ""),
147 "created_at": item_info.get("createTime", ""),
148 })
149 except Exception as e:
150 Actor.log.warning(f"Parse error: {e}")
151
152 results.append(data)
153 await actor.push_data(data)
154
155 except Exception as e:
156 Actor.log.error(f"Error scraping video: {e}")
157 await actor.push_data({"url": url, "error": str(e)})
158
159 await asyncio.sleep(2)
160
161 Actor.log.info(f"Done! Processed {len(results)} items.")
162
163
164if __name__ == "__main__":
165 asyncio.run(main())