Text Cleanup and URL De-tracking avatar

Text Cleanup and URL De-tracking

Pricing

from $0.40 / 1,000 cleaned texts

Go to Apify Store
Text Cleanup and URL De-tracking

Text Cleanup and URL De-tracking

Cleans a list of texts in one pass: strips HTML, repairs mojibake, tidies Unicode and removes URL trackers. $0.0005 per text.

Pricing

from $0.40 / 1,000 cleaned texts

Rating

0.0

(0)

Developer

Comrade Coding

Comrade Coding

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Security

Limited permissions

Share

Returns one row per input text: text (cleaned), with counts of what changed. In one pass it:

  • strips HTML tags and decodes references such as & and é,
  • repairs mojibake, UTF-8 text that was read as Windows-1252 or Latin-1 (Café becomes Café, we’ll becomes we’ll),
  • removes invisible characters and normalizes Unicode and whitespace,
  • removes utm_ parameters and 30 known click and email trackers (fbclid, gclid, msclkid, mc_eid and others) from every URL in the text.

It is made for the step right after a scrape or an export: paste or pipe in a list of texts, get back clean text in the same order. It reads only the text you send. It fetches nothing from the web.

Input

FieldTypeDefaultWhat it does
textsarrayrequiredThe texts to clean. Each entry is a string, or an object { "id": "...", "text": "..." }. The id is copied to the output row so you can match rows back to your records. Up to 500,000 characters per text.
htmlModestringautoauto: strip tags when the text contains HTML tags, otherwise only decode references such as &, so plain text keeps its line breaks. strip: always treat the text as HTML. keep: leave tags and references alone.
fixMojibakebooleantrueRepair UTF-8 text that was read as Windows-1252 or Latin-1. Also repairs text that was read wrongly twice.
unicodeFormstringNFCNFC, NFKC, NFD, NFKD or none. NFKC also turns ligatures (fi) and full-width letters (ABC) into plain ones.
whitespacestringlineslines: collapse spaces and tabs inside each line, trim lines, keep at most one blank line in a row. collapse: one line with single spaces. keep: no changes.
cleanUrlsbooleantrueRemove tracking parameters from every http and https URL in the text.
maxItemsinteger1000Stop after this many output rows.

Example input:

{
"texts": [
"<p>Café opening hours: Mon to Fri, 8 am to 4 pm.</p><p>Menu: <a href='https://example.com/menu?utm_source=newsletter&utm_medium=email&lang=en'>https://example.com/menu?utm_source=newsletter&utm_medium=email&lang=en</a></p>",
{ "id": "review-17", "text": "Great service, we’ll be back. Booked via https://example.org/book?ref=home&fbclid=IwAR0abc123 &amp; paid at the door." }
]
}

Output

One row per text that had any text in it, in input order. These two rows are real output from the test run for the example input above. The link inside the <a> tag goes away with the tag, so the first row counts only the URL left in the text:

{
"input_index": 0,
"id": null,
"processed_at": "2026-10-29T16:30:50.526Z",
"text": "Café opening hours: Mon to Fri, 8 am to 4 pm.\n\nMenu: https://example.com/menu?lang=en",
"input_chars": 225,
"output_chars": 85,
"changed": true,
"html_stripped": true,
"mojibake_fixed": 3,
"urls_changed": 1,
"params_removed": 2
}
{
"input_index": 1,
"id": "review-17",
"processed_at": "2026-10-29T16:30:50.530Z",
"text": "Great service, we’ll be back. Booked via https://example.org/book?ref=home & paid at the door.",
"input_chars": 121,
"output_chars": 94,
"changed": true,
"html_stripped": false,
"mojibake_fixed": 1,
"urls_changed": 1,
"params_removed": 1
}
FieldMeaning
input_indexPosition of the text in your texts list, starting at 0.
idThe id you sent with the text, as a string, or null.
processed_atWhen the row was made (UTC).
textThe cleaned text.
input_chars, output_charsLength before and after, in UTF-16 code units (the same as JavaScript's length).
changedtrue when the text differs from the input in any way.
html_strippedtrue when the text was treated as HTML.
mojibake_fixedHow many broken character sequences were repaired.
urls_changedHow many URLs had tracking parameters removed.
params_removedHow many tracking parameters were removed in total.

Two records go to the run's key-value store, not the dataset, so they are never charged:

  • SUMMARY: counts for the run (rows pushed, rows changed, mojibake repairs, parameters removed, skipped entries) and stopped_by (done, maxItems, or charge limit).
  • FAILED_INPUTS: entries that were skipped and why: empty text, not a string or { "text": ... } object, or over 500,000 characters.

What each step does

HTML. Tags and comments are dropped, and so is the content of script, style, template and noscript. Paragraphs, headings, lists and tables become blank lines; list items, rows and <br> become line breaks. A < that does not start a tag (a < b) stays as text, and AT&T stays AT&T. In auto mode a text counts as HTML only when it has a known HTML tag (p, br, div, span, a, li, h1 to h6, table, img, b, i, strong and 69 others) written bare (<p>, </b>, <br/>) or with an attribute value (<a href="...">), or a comment. So if a<b and c>d, List<String> and <name> stay plain text.

Mojibake. Each suspicious sequence is mapped back to the bytes it came from and read as UTF-8. Nothing changes unless those bytes are valid UTF-8. Two or more broken characters in a row are always repaired; that covers whole words in any script, Chinese, Japanese, Korean, Hebrew and Arabic included. A single broken character standing alone is repaired only when it is one that broken text produces all the time: accented letters from Latin-1 and Latin Extended-A (é, ñ, ł, š), Romanian ș and ț, Greek, Cyrillic, Vietnamese letters, punctuation such as curly quotes, dashes and the no-break space, currency signs, symbols and emoji. A lone two-byte repair is also skipped when it comes right after an upper case letter and no lower case letter follows, because correct all-caps text such as IRMÃ… has the same shape. Names such as São Paulo and Müller are still repaired. Up to three passes run, so text that was broken twice (’) comes back too.

Unicode and whitespace. Removes zero width space, word joiner, byte order mark, soft hyphen and control characters other than tab and line breaks. Zero width joiner and non-joiner are kept, since emoji and several scripts need them. Then applies the normalization form and the whitespace mode. In lines and collapse mode, no-break spaces become plain spaces.

Tracking parameters. Removed: any parameter whose name starts with utm_, and fbclid, gclid, gclsrc, dclid, gbraid, wbraid, msclkid, mc_cid, mc_eid, igshid, yclid, twclid, ttclid, li_fat_id, _hsenc, _hsmi, __hstc, __hssc, __hsfp, hsctatracking, mkt_tok, _gl, ref_src, oly_anon_id, oly_enc_id, vero_id, vero_conv, s_cid, rb_clickid, wickedid. Names are compared without regard to case, and percent-encoded names are read too. Both & and &amp; (an href kept with htmlMode: keep) count as separators, also mixed in one query, and each kept parameter keeps the separator written before it. Every other part of the URL stays byte for byte as it was. Punctuation at the end of a sentence (., ,, ) without a matching () is not treated as part of the URL.

Limits

  • It does not detect the language or the original encoding of a whole file. It repairs the common case, UTF-8 read as Windows-1252 or Latin-1. Text broken the other way round, or through other code pages (for example Cyrillic Windows-1251 read as UTF-8), is left as it is.
  • When a broken sequence has lost a byte on the way (some tools drop the invisible character that ends †for a closing quote), the leftover is not guessed at and stays in the text.
  • A single broken character standing alone is left as it is when it would decode to a rare character (one Chinese, Hebrew or Arabic letter on its own), or when it comes right after an upper case letter with no lower case letter after it (CAFé and SÃO PAULO stay, while Café, São Paulo and CAFÉ’S are repaired). Correct text such as café…” or IRMÃ… looks the same as those cases. A letter plus a quote or dash that would become an upper case Latin Extended-A letter with no letter after it (Ä’) also stays.
  • Correct text can still be changed in rare cases: a lower case or non-letter character followed by one of the common pairs above, for example xé written on purpose, or à after a space followed by a no-break space. Set fixMojibake: false for text that is known to be correct.
  • In auto mode, plain text that contains a real HTML tag name in tag form, such as <b> or <p>, is treated as HTML and loses its line breaks. Use htmlMode: keep for such text.
  • It does not follow URLs, expand short links, or remove tracking parameters other than those listed above.
  • 500,000 characters per text. Longer texts are skipped and listed in FAILED_INPUTS.
  • The output keeps whatever the text says. Nothing is rewritten except the steps above.

Pricing

Pay per event: $0.0005 per output row ($0.50 per 1,000 texts). Skipped entries are free. Volume tiers: Free and Bronze at the list price, Silver $0.00045, Gold $0.0004.

Run sizeCost
100 textsabout $0.05
1,000 textsabout $0.50
10,000 textsabout $5

The default maxItems of 1,000 caps a run at about $0.50. A run also stops when it reaches the cost limit you set for it on Apify.

Use it from an AI agent

Add this actor as a tool through Apify's MCP server: https://mcp.apify.com?tools=comradecoding/text-cleanup. The agent sends a list of texts and gets back one row per text with the cleaned text and counts of what changed.

Integrations

Anything that can call the Apify API can run this actor: the Apify API and client libraries, and Apify's integrations for Make, n8n, Zapier and LangChain. Results come back as a dataset you can download as JSON, CSV or Excel.

FAQ

Does it change text that is already correct? Only by the steps you leave on: whitespace tidying, Unicode normalization, removal of invisible characters and tracking parameters. The mojibake step leaves correct text alone in all the cases the tests cover (French, German, Portuguese, Vietnamese, curly quotes, upper case words, CJK), with the rare exceptions under Limits. Set fixMojibake: false, whitespace: keep and unicodeForm: none for the lightest touch.

Why did my line breaks disappear? The text was treated as HTML (it had a tag), and HTML line breaks come from tags, not from newlines. Use htmlMode: keep for plain text that happens to contain tags.

Can I clean a field from another actor's dataset? Map that field into texts with the Apify API, Make, n8n or Zapier, and pass the record key as id so you can join the rows back.

Is my text stored? Only in your own run's dataset and key-value store on Apify, under your account's retention settings. The actor makes no network requests.

  • URL to Markdown: turn web pages into clean Markdown, then clean any leftover text here.
  • RSS to JSON: feed items as JSON; the titles and summaries often carry mojibake and tracking links.
  • Sitemap URL Extractor: list a site's pages.
  • PDF to Text: text from PDF files, often with ligatures that NFKC turns into plain letters.

I built and tested it with help from an AI coding assistant (Claude); 49 automated tests cover the behavior described here.

Not affiliated with example.com, example.org, or any company named in the tracker list. The tracker names are listed only to say which URL parameters are removed.