> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xquik.com/llms.txt
> Use this file to discover all available pages before exploring further.

# X (Twitter) API Python tweet search to CSV & JSONL

> Search tweets with the Xquik Python SDK, follow cursors, and save rows as JSON Lines, CSV, or XLSX for workers, queues, and analysts in your own apps and jobs.

> For the complete documentation index, see [llms.txt](https://docs.xquik.com/llms.txt).

This guide builds on the [Python SDK setup](https://docs.xquik.com/sdks/python.md), which covers install and authentication.

## Tweet search workflow to CSV, JSON Lines, or XLSX

Search with `client.x.tweets.search`, `GET /x/tweets/search`, and generated `TweetSearchParams`. Save CSV and JSON Lines for notebooks, workers, agents, queues, warehouses, and replay.

```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/bash.json","/languages/csharp.json","/languages/go.json","/languages/java.json","/languages/javascript.json","/languages/kotlin.json","/languages/php.json","/languages/python.json","/languages/ruby.json","/languages/typescript.json"]}}
import csv
import json
from pathlib import Path

from x_twitter_scraper import XTwitterScraper

client = XTwitterScraper()
query = "from:username webhook OR SDK"
csv_output = Path("xquik-tweet-search.csv")
jsonl_output = Path("xquik-tweet-search.jsonl")
cursor = None
page_index = 0

with csv_output.open("w", newline="", encoding="utf-8") as csv_file, jsonl_output.open(
    "w",
    encoding="utf-8",
) as jsonl_file:
    writer = csv.DictWriter(
        csv_file,
        fieldnames=[
            "source",
            "query",
            "tweet_id",
            "text",
            "author_id",
            "author_username",
            "author_name",
            "created_at",
            "like_count",
            "reply_count",
            "retweet_count",
            "quote_count",
            "view_count",
            "bookmark_count",
            "is_note_tweet",
            "page_index",
            "page_cursor",
            "next_cursor",
            "has_next_page",
        ],
    )
    writer.writeheader()

    while True:
        page_cursor = cursor
        page = client.x.tweets.search(
            q=query,
            query_type="Latest",
            cursor=cursor,
        )

        for tweet in page.tweets:
            row = {
                "source": "xquik.python.search",
                "query": query,
                "tweet_id": tweet.id,
                "text": tweet.text,
                "author_id": tweet.author.id if tweet.author else None,
                "author_username": tweet.author.username if tweet.author else None,
                "author_name": tweet.author.name if tweet.author else None,
                "created_at": tweet.created_at,
                "like_count": tweet.like_count or 0,
                "reply_count": tweet.reply_count or 0,
                "retweet_count": tweet.retweet_count or 0,
                "quote_count": tweet.quote_count or 0,
                "view_count": tweet.view_count or 0,
                "bookmark_count": tweet.bookmark_count or 0,
                "is_note_tweet": tweet.is_note_tweet or False,
                "page_index": page_index,
                "page_cursor": page_cursor,
                "next_cursor": page.next_cursor or None,
                "has_next_page": page.has_next_page,
            }
            writer.writerow(row)
            jsonl_file.write(json.dumps(row, ensure_ascii=False) + "\n")

        if not page.has_next_page or not page.next_cursor:
            break
        cursor = page.next_cursor
        page_index += 1
```

The method accepts the same query inputs as the REST endpoint.

> [card] q
>
> Python argument `q` maps to REST `q`. It holds the required X (Twitter) search query with keywords, handles, hashtags, or operators.

> [card] limit
>
> Python argument `limit` maps to REST `limit`. Use it as a 1 to 200 upper bound for a bounded pull. If `page.has_next_page` is true, keep the same `q`, filters, `query_type`, and `limit`. That applies when you continue with `page.next_cursor`.

> [card] cursor
>
> Python argument `cursor` maps to REST `cursor`. Pass the opaque cursor from `page.next_cursor` to request the next page.

> [card] since\_time
>
> Python argument `since_time` maps to REST `sinceTime`. Use it as the ISO 8601 lower time bound.

> [card] until\_time
>
> Python argument `until_time` maps to REST `untilTime`. Use it as the ISO 8601 upper time bound.

> [card] query\_type
>
> Python argument `query_type` maps to REST `queryType`. Use `Latest` for chronological results or `Top` for engagement-ranked results.

## Returned data & handoff

`client.x.tweets.search` returns a `PaginatedTweets` Pydantic model.

> [card] page.tweets
>
> JSON field `tweets`. Contains `SearchTweet` records with `id`, `text`, optional `author`, and `created_at`. Records also carry `like_count`, `reply_count`, `retweet_count`, `quote_count`, `bookmark_count`, `view_count`, and `is_note_tweet` when available.

> [card] page.has\_next\_page
>
> Python field `page.has_next_page`. JSON field `has_next_page`. Tells your script whether another page exists.

> [card] page.next\_cursor
>
> JSON field `next_cursor`. Store it only when `page.has_next_page` is true. For bounded pulls that return fewer tweets than `limit`, pass it back as `cursor`. Send the same query, filters, `query_type`, and `limit` with it.

Project `page.tweets` into CSV rows for analysts. Project them into JSON Lines rows in `xquik-tweet-search.jsonl` for queues and data lakes. If account teams need an XLSX workbook, load those rows into pandas or openpyxl. Store `tweet_id`, `author_username`, engagement counts, `page_index`, `page_cursor`, `next_cursor`, and `has_next_page`. With them, scripts resume from the last saved cursor without replaying raw SDK models. For explicit `limit` pulls, resume with the same query, filters, `query_type`, and `limit`. Only `cursor` changes.

## Next steps

* [Python SDK setup](https://docs.xquik.com/sdks/python.md)
* [Follower export](https://docs.xquik.com/sdks/python-follower-export.md)
* [Media tweets](https://docs.xquik.com/sdks/python-media-tweets.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.