Social media trend analysis starts with a simple question: “What are people starting to talk about, and how do I catch it before it becomes obvious?”
You could scroll through feeds, follow hashtags, and check what each platform labels as trending. But that only gives you part of the picture. This happens because conversations move across platforms, engagement changes over time, and what looks like growing interest might just be the same content reposted over and over.
That’s where social media scraping comes in and makes the game easier. If you’re wondering why, the answer is simple: it lets you collect posts and engagement metrics systematically, then track how conversations evolve. However, collecting the data isn’t enough. You need a pipeline that separates meaningful signals from noise, handles differences between platforms, and accounts for the legal requirements of collecting and using social data.
In this article, we’ll discuss how to choose your sources, build the collection infrastructure, extract and clean trend signals with Python, and turn them into useful analysis.
Give your AI a web data layer – Decodo’s Web Scraping API turns any site into clean, structured data your models can actually use.
Understanding the Social Media Data Landscape
Before writing code, you need to map out the platform landscape you’re targeting. Social media scraping isn’t a one-size-fits-all discipline because each platform exposes data differently, enforces its own limits, and deploys different anti-bot measures.
So, you can’t just pick a social media platform at random and think to apply a scraping strategy to other platforms. You must align your platform choice with your trend-analysis goals and with the technical features each platform has.
Checking for Platform-Specific Data Structures
Once you’ve picked your social media targets, you’ll quickly notice that no two platforms structure their data the same way:
Some platforms expose posts and engagement metrics through JSON-based GraphQL responses.
Others return JSON too, but through obfuscated and session-dependent internal APIs. And these are harder to reverse-engineer.
Others rely on server-rendered HTML. And this requires a more fragile extraction, as it is selector-based. But remember that, in such cases, a possible solution is using GPT vision to extract the data.
To make an example, X (formerly Twitter) fetches data through internal GraphQL endpoints visible in the browser through the DevTools:
Instead, LinkedIn fetches feed and post data through internal JSON API calls using obfuscated endpoint paths, rather than readable REST routes:
This means your social media scraping pipeline can’t use a single generic extraction strategy. You need tailored logic for each source, focusing on the specific social media.
API vs. Direct Scraping Trade-offs
You’ll also have to decide early on whether to lean on official APIs or go the direct scraping route. APIs give you stability and predictable schemas, but they often come with strict rate limits, high costs, and severe limitations regarding the data you can retrieve.
For instance, X is very specific about how developers should use their APIs and what happens if they don’t:
Even the LinkedIn API is very restrictive on use cases. For example, the public API doesn’t expose feed/post/engagement data for general use. This means that access is essentially locked to partners (limited marketing/talent solutions):
Direct scraping, on the other hand, gives you far more flexibility and access to richer data, but it raises the stakes. Basically, you’re taking on:
Greater legal exposure.
A heavier maintenance burden since platforms change their front-end structure without warning.
Infrastructure management. You need to account for: geo-targeted proxies, a wide pool of residential proxies, CAPTCHA-solving capabilities, throttle and rate limiting management as these are high-traffic websites, and anything in between.
Only pay for data you actually get — one request, one credit, every time. No surcharge for JS rendering, no charge when a call gets blocked. Claim your free 10,000 requests with coupon code WEBSCR
Legal and Ethical Considerations When Scraping Social Media for Trends
Before moving into code and infrastructure, you need to think through the legal terrain you’re operating in. Otherwise, the risk is getting into legal trouble once you get the data you need.
If you’ve been web scraping for a while, you know that accounting for ethical web scraping is a best practice for any scraping case. But note that social media scraping is a delicate case. This is because it sits at the intersection of platform terms of service, privacy regulations like GDPR and CCPA, and copyright law that governs user-generated content.
This means that if you want to stay safe, you need a compliance framework in place from the beginning. And this matters even if you’re only collecting public posts for trend analysis. For this specific case, this is what matters most:
Compliance with platform terms of service: Most platforms explicitly ban automated access in their terms of service, and they don’t always distinguish between a well-intentioned research project and a malicious bot. If you violate those terms, you’re risking account bans, IP blocks, or even legal action from the platform itself. That’s why you want to do a proper risk assessment before you start scraping.
Handling personally identifiable information (PII): Even when you’re only after trend data, you’ll almost certainly capture PII (usernames, bios, locations, and sometimes more) along the way. That’s where things get tricky under regulations like GDPR, which don’t care whether the data was “public” when you scraped it. If you think you need this kind of data for some reason, you better build in an anonymization layer and apply it to the data.
Respecting robots.txt: That’s probably the evergreen rule. And at The Web Scraping Club, we deeply discussed the implications of the robots.txt file. Remember that it’s not legally binding on its own, but ignoring it undermines your compliance framework. And if you do that, it can be used as evidence of bad faith if a platform pursues legal action over your social media scraping activity. So, treat it as a baseline signal of the platform’s intent, then layer your ToS and privacy review on top of it rather than relying on robots.txt alone.
And for the sake of being tied to reality, let’s check LinkedIn’s robots.txt file:
The above image reports only the initial part of it. But, as you can see, it’s very clear: you can not use automated software to access LinkedIn unless you ask for specific permission via e-mail. And you’re not sure you’ll get permission from LinkedIn, of course.
Building a Scalable Scraping Architecture
A trend-analysis pipeline isn’t a simple scraper running on a cron job. Once you’re dealing with high-velocity, high-volume streams of short-lived social content, you need distributed crawling, proxy rotation, and storage systems built to keep up with the momentum.
So, let’s consider the case where you decided to go with your custom scraper because the platform APIs don’t cover your case. In that case, after assessing the legal risk and defining the legal framework you’ll work on, you need to build the infrastructure.
Proxy Rotation and Anti-Detection Techniques
Social platforms don’t take kindly to automated traffic, and their anti-bot systems have become aggressive because they use machine learning to intercept detection triggers. To stay under the radar, you need to master at least the following:
Residential proxies, since they’re far less likely to get flagged than datacenter IPs.
Header randomization, so your requests don’t all look like they’re coming from the same bot.
Browser fingerprint spoofing, which helps you avoid the CAPTCHAs and soft bans that kick in the moment a platform notices a suspicious pattern.
None of these alone is a silver bullet. But combined, they buy you the runway you need to keep collecting data without getting IP-banned every few hours.
Headless Browsers vs. Lightweight HTTP Clients
Here’s where you need to be strategic about resource allocation. Dynamic, JS-rendered feeds (think infinite-scroll timelines) often leave you no choice but to use headless browsers like Playwright or Puppeteer, since the content simply isn’t there until JavaScript executes. But that comes at a cost: headless browsers are slow and resource-hungry.
So, for static or semi-static endpoints, you’re better off with lightweight HTTP-based scrapers. They’re faster, cheaper to run at scale, and don’t need a full browser context spun up just to grab a JSON payload. The trick is knowing which endpoints actually need the heavy tooling and which don’t.
Scheduling and Real-Time Data Pipelines
Trend detection is a race against freshness. And a trend you catch six hours late isn’t a trend anymore. That’s why your scheduling strategy matters as much as your extraction logic. The rule of thumb is the following:
Cron-based batch jobs work fine if you’re tracking slower-moving trends and can tolerate some lag.
Streaming architectures (Kafka, message queues) are what you need if you’re after near-real-time detection. This is because, in that case, you need to process data as it arrives instead of waiting for the next batch window.
Which one you pick really comes down to how fast you need to know something’s trending, and how much infrastructure complexity you’re willing to take on to get there.
What If You Don’t Want to Manage The Infrastructure?
If you’re a scraping expert, you certainly know the burden of managing the scraping infrastructure: residential proxies are very expensive, CAPTCHAs block you, selectors change every one in a while, parallel requests take time to execute, and so on. Furthermore, the legalities of social media scraping get you up at night.
So…are there any other alternatives other than using official platform APIs and scraping on your own? The answer is yes!
As you know, the scraping market is strong, and lots of companies provide several services in it. Among the companies that provide scraping services, some offer social media scraping services. I don’t want to end up in a list here because everything is easily searchable on your favourite provider’s website. Just note that the majority of them provide pre-collected datasets and/or scraping APIs to retrieve data from social media.
In simple terms, you just need to make an API call and collect posts from Instagram, Facebook, TikTok, or whatever social media you’re targeting. The advantages of this approach are the following:
The scraping service manages the infrastructure for you.
You don’t need to manage the legalities behind the data extraction part. But, yes: you still need to account for the legalities of anyting related, like data retention, storage, and usage.
If you’re ambitious and want to scale, you can do it along with the scraping service you’re using.
You don’t need to think about how the platform displays the data. Either they expose APIs or render HTML, you make an API call, and you’re good to go.
The price is generally fair. I mean: if you don’t own proxies or any other scraping infrastructure, API calls for retrieving data from social media platforms need just a few dollars. Furthermore, depending on the scale of your project, you may simply benefit from the service’s free trial.
So, if you’re scraping social media to intercept trends, the suggestion is to take a look at these solutions before writing your own custom code. You may be surprised by how you can find the right solution for your case.
Data Management Strategies for Trend Signals
Grabbing raw posts from social media is only half the job. And that’s it, regardless of the data extraction method you used. To actually detect trends, your social media scraping pipeline needs to extract structured signals from content noise.
In social media posts, the fields that actually tell you if something is trending are hashtags, topics, engagement velocity, and mention frequency. So, let’s see how to manage these.
Extracting Hashtags, Mentions, and Keywords
Post text is messy. So, before you can count anything meaningful, you need to isolate the actual trend indicators from the surrounding noise. A “traditional” approach to do that is using regex.
That method gets you most of the way for hashtags and mentions. For example, consider the code below:
import re
from collections import Counter
def extract_signals(text):
hashtags = re.findall(r"#(\w+)", text)
mentions = re.findall(r"@(\w+)", text)
return hashtags, mentions
posts = [
"AI is eating the world #AI #MachineLearning cc @openai",
"New drop today #sneakers #AI incoming",
]
hashtag_counter = Counter()
mention_counter = Counter()
for post in posts:
tags, mentions = extract_signals(post)
hashtag_counter.update(tags)
mention_counter.update(mentions)
print(hashtag_counter.most_common()) NOTE: For the sake of simplicity, the posts[] list reports short content. An example from a complete scraping scenario works by opening a CSV or JSON file, extracting the content, and applying regex.
The result from such a basic example is the following:
[('AI', 2), ('MachineLearning', 1), ('sneakers', 1)]The above result is a list of tuples. Each tuple shows the hashtag and how many times it appears in the analyzed content. This is an elegant result because regex gets you hashtags and mentions cleanly, but it won’t catch implicit topics people are talking about without tagging them. For that case, you need more than regex. A solution is to use NLP-based tokenization for entity extraction on top of your script, like the following:
# pip install spacy
# python -m spacy download en_core_web_sm
import spacy
nlp = spacy.load("en_core_web_sm")
def extract_keywords(text):
doc = nlp(text.lower())
return [
token.lemma_ for token in doc
if not token.is_stop and not token.is_punct and token.pos_ in ("NOUN", "PROPN")
]
keywords = extract_keywords("New drop today, the sneakers are insane")
print(keywords) And the result is:
['drop', 'today', 'sneaker']In that case, this content can tell you:
drop → Something has it or is hitting the market.
today → That something occurred today.
sneaker → The actual product.
Combine both approaches, and you’ve got a keyword pool you can feed into frequency counting or topic clustering downstream, without hand-tagging every post yourself.
As a final consideration, note that the NLP-based approach also has limitations. The main argument we could discuss is that this solution is based on the vocabulary you use. So, another technique is to use AI to extract entities and patterns from text. If you want to deepen that, we already covered this in the article “Using AI to Detect Patterns in Scraped Data”.
Capturing Engagement Metrics Over Time
Hashtags, mentions, and keywords tell you what people are talking about. Engagement metrics tell you how fast the news is spreading. And that’s a moving target. This means that you can’t scrape a post once and call it a day. You need to hit the same post at certain time intervals and build a time series out of it. The following code helps you do that:
import time
import csv
from datetime import datetime
def scrape_engagement(post_id, fetch_fn):
data = fetch_fn(post_id)
return {
"post_id": post_id,
"timestamp": datetime.utcnow().isoformat(),
"likes": data["likes"],
"shares": data["shares"],
"comments": data["comments"],
"views": data.get("views", 0),
}
def track_post(post_id, fetch_fn, interval_sec=900, iterations=8):
with open(f"{post_id}_engagement.csv", "a", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["post_id", "timestamp", "likes", "shares", "comments", "views"])
if f.tell() == 0:
writer.writeheader()
for _ in range(iterations):
row = scrape_engagement(post_id, fetch_fn)
writer.writerow(row)
f.flush()
time.sleep(interval_sec) In this example code:
fetch_fnis whatever extraction logic you’re using for that specific platform.The
interval_secanditerationsare up to you. Tighter intervals catch virality earlier, but they also burn through your proxy pool faster.
Once you’ve got this data logged, you can compute engagement velocity (delta between snapshots) and feed it straight into the time-series analysis you’ll run downstream. Then, build a dashboard - for example, using Streamlit - and use Pandas, Matplotlib, and related libraries to analyze the scraped data and get trends out of your time series.
Data Cleaning and Normalization
Social media trends don’t live in a single platform. A topic can break on Reddit, spike on X six hours later, and only show up on TikTok in a couple of days. So, if you’re only scraping one source, you risk tracking a fragment of the whole trend. As a consequence, if raw scraped data is messy by default when you get it from a single source, you can imagine the chaos with multiple sources.
This means your pipeline needs to standardize data across platforms. Otherwise, you’ll be comparing apples to oranges without realizing it. So let’s see some techniques to do so.
Deduplication and Bot/Spam Filtering
Bots and spammers are a real trouble for the web (they were so even in the “forums era”, if you’re old enough to remember it 😁). In the context of trend detection, if you consider them and reposts, you get inflated engagement. In other words, if you don’t filter them out, you’ll end up “detecting” trends that are really just coordinated inauthentic behavior.
Deduplication is the easy part. For that, content hashing gets you most of the way there as follows:
import hashlib
posts = [
{"text": "AI is eating the world #AI #MachineLearning"},
{"text": "AI is eating the world #AI #MachineLearning"}, # exact duplicate
{"text": " ai IS eating the world #AI #MachineLearning "}, # duplicate after normalization
{"text": "New drop today #sneakers #AI incoming"},
{"text": "Another unrelated post about crypto"},
]
def content_hash(text):
normalized = text.strip().lower()
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
seen_hashes = set()
unique_posts = []
for post in posts:
h = content_hash(post["text"])
if h not in seen_hashes:
seen_hashes.add(h)
unique_posts.append(post)
print(f"Original posts: {len(posts)}")
print(f"Unique posts: {len(unique_posts)}")
for p in unique_posts:
print("-", p["text"])This code:
Defines a
content_hash()function that strips whitespace and lowercases the post text, then computes a SHA-256 hash of it. Note that normalizing before hashing means that formatting differences (extra spaces, capitalization) don’t prevent identical content from being detected as duplicates.Iterates over the list of
posts, computing a hash for each one.Keeps a
seen_hashesset to track which content hashes have already been encountered.
So, if a post’s hash is new, it’s added to both seen_hashes and the unique_posts list; if the hash already exists, the post is skipped as a duplicate. The result, unique_posts, contains only the first occurrence of each distinct piece of content. When runing it, you’ll see the following:
Original posts: 5
Unique posts: 3
- AI is eating the world #AI #MachineLearning
- New drop today #sneakers #AI incoming
- Another unrelated post about cryptoNow, the code above accounts only for exact matches. However, that won’t catch near-duplicates (slightly reworded reposts). For that, you’ll want something like MinHash or embedding similarity:
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("all-MiniLM-L6-v2")
posts = [
{"text": "AI is eating the world"},
{"text": "Artificial intelligence is taking over the world"},
{"text": "New sneaker drop today"},
{"text": "Sneakers just dropped today"},
{"text": "Completely unrelated topic about cooking pasta"},
]
def find_near_duplicates(posts, threshold=0.6):
texts = [p["text"] for p in posts]
embeddings = model.encode(texts, convert_to_tensor=True)
duplicates = []
for i in range(len(texts)):
for j in range(i + 1, len(texts)):
sim = util.cos_sim(embeddings[i], embeddings[j]).item()
if sim > threshold:
duplicates.append((i, j, sim))
return duplicates
results = find_near_duplicates(posts, threshold=0.6)
for i, j, sim in results:
print(f"({i}, {j}) sim={sim:.3f} -> '{posts\[i\]['text']}' | '{posts\[j\]['text']}'")This snippet does the following:
Loads a pre-trained sentence embedding model (
all-MiniLM-L6-v2) viaSentenceTransformer.Encodes each post’s text into a dense vector embedding that captures its semantic meaning, not just exact wording.
Compares every pair of posts using cosine similarity (
util.cos_sim) to measure how semantically close their content is.Flags pairs whose similarity score exceeds the given
thresholdas near-duplicates. This catches reworded or paraphrased reposts that exact-hash matching (like the previouscontent_hashexample) would miss.Returns a list of tuples
(i, j, similarity_score)identifying which post pairs are likely duplicates.
As this solution uses machine learning, the actual similarity scores depend on the model run. So, the score you get can be slightly different from the following:
(0, 1) sim=0.72 -> 'AI is eating the world' | 'Artificial intelligence is taking over the world' (2, 3) sim=0.81 -> 'New sneaker drop today' | 'Sneakers just dropped today'And, yes: this is a pretty good result!
Normalizing Timestamps and Multilingual Text
Cross-platform trend detection breaks down fast if you don’t normalize timestamps. This is because every platform reports time differently: some give you UTC ISO strings, others hand you Unix epochs, and some (looking at you, scraped HTML!) give you relative strings like “3h ago” that you have to resolve at scrape time before they become meaningless.
Timestamp conversion is the easy of the two:
from datetime import datetime, timezone
import dateutil.parser
def normalize_timestamp(raw_ts):
if isinstance(raw_ts, (int, float)):
return datetime.fromtimestamp(raw_ts, tz=timezone.utc)
return dateutil.parser.parse(raw_ts).astimezone(timezone.utc)
print(normalize_timestamp(1735689600))
print(normalize_timestamp("2025-01-01T00:00:00+05:30"))But relative timestamps need to be resolved against your scrape time, not stored as-is:
import re
from datetime import timedelta
def resolve_relative_time(raw, scraped_at):
match = re.match(r"(\d+)([hmd])", raw)
if not match:
return scraped_at
value, unit = int(match.group(1)), match.group(2)
delta = {"m": timedelta(minutes=value), "h": timedelta(hours=value), "d": timedelta(days=value)}[unit]
return scraped_at - deltaIf you think that managing time is hard, consider that language is the harder problem. A trend can absolutely break in a non-English-speaking market first, and if your pipeline only handles English, you’ll miss it entirely. But the problem is that multilingual analysis can’t just be “run everything through a translator”. That’s where a lot of “translation” pipelines get sloppy.
To account for that, you first need language detection:
# pip install langdetect
from langdetect import detect
def detect_language(text):
try:
return detect(text)
except Exception:
return "unknown"
texts = [
"AI is eating the world, and everyone is talking about it.",
"L'intelligence artificielle est en train de conquérir le monde.",
]
for t in texts:
print(f"'{t}' -> {detect_language(t)}")And the result you get is as expected:
'AI is eating the world, and everyone is talking about it.' -> en
'L'intelligence artificielle est en train de conquérir le monde.' -> frThen, you can go through translation. Beyond translation quality, tokenization itself gets messy across languages. Whitespace-based tokenizers (fine for English) fall apart on languages without clear word boundaries, like Japanese or Chinese, where you need dedicated segmenters.
Overall, covering every nuance of multilingual translation and tokenization in depth is beyond the scope of this article. But knowing where these pitfalls lie helps you design a pipeline that’s ready to handle them as your trend-detection needs grow.
Analyzing Trends from Scraped Data
Clean data is a reliable starting point, but it doesn’t tell you what’s trending. Now, your social media scraping pipeline needs to distinguish normal conversation from unusual growth, identify what people are actually discussing, and measure how that discussion changes over time.
And there’s an important distinction to make: a popular topic isn’t necessarily an emerging trend. A topic with consistently high mention volume might just be part of the platform’s normal activity. Meanwhile, a smaller conversation that’s spreading across independent communities could deserve your attention much earlier.
Basically, you need two complementary layers of analysis:
Time-series analysis for virality detection: Measure changes in mention volume, participation, and engagement against an appropriate baseline.
Sentiment analysis and topic modeling: Identify the themes behind those changes and understand how people feel about the specific subjects involved.
Both kinds of analysis can require a lot of work and, surely, require specialized engineers. So, allow me to just give you the high-level ideas that matter.
Time-Series Analysis for Virality Detection
Start by deciding what you’re counting and when you’re counting it. This sounds obvious, but it’s where you can accidentally turn a collection artifact into a trend.
For mention volume, you should group posts into fixed time windows using their publication timestamps. For engagement growth, use the timestamps of your repeated metric observations. These aren’t interchangeable, because a post published yesterday can receive new engagement today.
So, your time-series dataset should distinguish:
Mention volume: The number of distinct collected posts mentioning a topic within each window.
Unique authors: The number of distinct participating accounts, measured within each platform unless cross-platform identities are reliably linked.
Engagement increments: The change in a post’s likes, comments, shares, or views between observations.
Collection coverage: Whether collection succeeded, which sources were available, and whether sampling or query settings changed.
That last point matters more than it might seem because if your scraper stops collecting for an hour, you have missing observations.
Sentiment Analysis and Topic Modeling
Time-series analysis tells you that something changed. NLP helps you understand what changed.
But don’t make the mistake of collapsing sentiment and topic detection into the same task because:
A topic model groups related discussions.
A sentiment model estimates expressed polarity toward something.
This means that neither, on its own, explains why a conversation is growing.
Also, note that the results - especially related to sentiment analysis - highly depend on the specific neural network you’re using. This is a wide topic, and we already covered it with a practical tutorial. If you want to deepen it, check out our article “Sentiment Analysis on Scraped Product Reviews: Step-by-step Tutorial”.
Putting It All Together: From Social Media Scraping to Trend Insights
At this point, you’ve seen the main pieces of the pipeline. But the actual value comes from connecting them. Social media scraping gets you the posts; extraction, cleaning, and analysis turn those posts into signals you can interpret.
So, before you start building, here’s how the whole process fits together:
In practical terms, these are the stages your pipeline needs to cover:
Define what you’re tracking and where you can collect it: Choose platforms around your trend-analysis goals, then review the relevant access rules and legal requirements. Using a scraping provider changes who operates the infrastructure, but it doesn’t automatically remove your responsibilities around data collection and use.
Collect posts and revisit engagement metrics: Decide whether to use official APIs, your own collectors, or managed services depending on your requirements. Store source identifiers, publication timestamps, and observation timestamps alongside the content. Remember that a dataset collected once can support historical analysis, but tracking engagement growth requires repeated observations.
Extract signals, then clean and normalize them: Isolate hashtags, mentions, keywords, and engagement counters. Standardize timestamps and handle language differences before comparing sources. Also, distinguish duplicate collection from actual reposting: collecting the same post twice is a data-quality issue, while independent reposts can be evidence of a conversation spreading. Don’t automatically discard that signal.
Analyze changes rather than raw popularity alone: Compare mention volume and engagement velocity against historical activity, then use topic modeling and sentiment analysis to understand the discussions behind those changes. And keep checking collection coverage.
Visualize the results and monitor the pipeline: We haven’t explored data visualization in depth here, but it’s the final layer that makes your results easier to interpret. A Streamlit dashboard, for example, can display mention-volume curves, engagement growth, and topic or sentiment breakdowns. Just make sure it also shows data freshness and collection gaps, so readers know what they’re looking at.
Overall, the thing to remember is this one: you don’t get reliable trends by only collecting more posts. You get them by keeping the whole pipeline consistent, from source selection to the dashboard. And when a spike appears, you should be able to trace it back to the underlying posts and collection conditions.
Conclusion
In this article, you learned that social media scraping for trend analysis is about more than collecting posts and counting hashtags. You need a pipeline that brings together platform-specific collection, clean data, repeated engagement measurements, and analysis that distinguishes unusual growth from normal activity.
The value is in how you connect those pieces. A spike in mentions doesn’t tell you much if you can’t tell whether it came from independent conversations, repeated content, or a change in your collection coverage. But a system that helps you understand what’s gaining attention, where it’s spreading, and how the discussion is changing? That’s where the real value is!
So, let us know: Are you using social media scraping to track trends? Which platforms are you collecting from, and what’s been the hardest part of turning those posts into useful signals? Let’s discuss in the comments!
Did you like this article? Share it with someone who might find it useful and get a discount on paid plans.









