nitter-scraper-v2
Version:
A Twitter scraper that uses Nitter to fetch tweets without authentication
860 lines (679 loc) • 27.7 kB
Markdown
# 🐦 Nitter Scraper V2
<div align="center">
**Advanced Twitter/X scraping solution** powered by Nitter with comprehensive proxy support and user profile extraction.
_Enhanced fork of the original [nitter-scraper](https://www.npmjs.com/package/nitter-scraper) package with extended features and enterprise-grade capabilities._
[](https://www.typescriptlang.org/)
[](https://nodejs.org/)
[](https://choosealicense.com/licenses/mit/)
**[GitHub Repository](https://github.com/FaresSofiane/nitter-scraper-v2)** • **[Author: Fares Sofiane](https://github.com/FaresSofiane)**
---
### 🌐 Language / Langue
[](#english) [](#français)
</div>
---
# English
<details open>
<summary><b>📖 Click to expand English documentation</b></summary>
A comprehensive TypeScript package for extracting tweets and user profiles from Nitter without requiring authentication. This enhanced fork of the original [nitter-scraper](https://www.npmjs.com/package/nitter-scraper) provides advanced proxy management, complete media extraction, user profile statistics, and high-performance concurrent processing.
## ✨ Key Features
- 🚀 **Authentication-free scraping** - No Twitter API keys required
- 👤 **Complete user profiles** - Extract user information, statistics, and banner images
- 🔄 **Intelligent pagination** - Automatic multi-page processing with cursor management
- 🛡️ **Advanced proxy support** - Multiple proxy modes with automatic failover
- 📷 **Comprehensive media extraction** - Images, videos, and preview cards
- 📊 **Detailed statistics** - Tweet engagement metrics and user profile stats
- ⚡ **High-performance modes** - Concurrent processing for faster data collection
- 🔧 **Full TypeScript support** - Complete type definitions and IntelliSense
- 🛠️ **Enterprise-ready** - Error handling, retry logic, and production stability
## 📦 Installation
```bash
# Install as a library
npm install nitter-scraper-v2
# Or install globally for CLI usage
npm install -g nitter-scraper-v2
```
## 🚀 Quick Start
### Basic Usage with User Profile
```typescript
import { fetchTweets } from "nitter-scraper-v2";
async function main() {
// Fetch tweets and user profile information
const result = await fetchTweets("elonmusk", 3);
// Display user profile information
if (result.userProfile) {
const profile = result.userProfile;
console.log(`=== USER PROFILE ===`);
console.log(`Name: ${profile.fullname} (@${profile.username})`);
console.log(`Description: ${profile.description}`);
console.log(
`Verified: ${
profile.isVerified ? `Yes (${profile.verificationType})` : "No"
}`
);
console.log(`Avatar: ${profile.avatarUrl}`);
console.log(`Banner: ${profile.bannerUrl}`);
console.log(`Joined: ${profile.joinDate}`);
console.log(`Location: ${profile.location}`);
console.log(`Website: ${profile.website}`);
console.log(`\n=== PROFILE STATISTICS ===`);
console.log(`Tweets: ${profile.stats.tweets.toLocaleString()}`);
console.log(`Following: ${profile.stats.following.toLocaleString()}`);
console.log(`Followers: ${profile.stats.followers.toLocaleString()}`);
console.log(`Likes: ${profile.stats.likes.toLocaleString()}`);
}
// Display tweets
console.log(`\n=== TWEETS (${result.tweets.length}) ===`);
result.tweets.forEach((tweet) => {
console.log(`${tweet.fullname} (@${tweet.username}): ${tweet.text}`);
console.log(
`📊 ${tweet.stats.likes} likes, ${tweet.stats.retweets} retweets`
);
if (tweet.videos.length > 0) {
console.log(`🎥 Videos: ${tweet.videos.length}`);
}
});
}
main().catch(console.error);
```
### High-Performance Concurrent Mode
```typescript
import { fetchTweets } from "nitter-scraper-v2";
async function fastScraping() {
// Enable concurrent mode for faster processing
const result = await fetchTweets(
"username",
5, // maxPages
false, // useProxies
undefined, // proxyOptions
true // useConcurrency
);
console.log(`Processed ${result.tweets.length} tweets efficiently`);
console.log(
`User: ${result.userProfile?.fullname} with ${result.userProfile?.stats.followers} followers`
);
}
```
## 📚 Complete API Reference
### fetchTweets Function
```typescript
function fetchTweets(
username: string,
maxPages?: number,
useProxies?: boolean,
proxyOptions?: ProxyOptions,
useConcurrency?: boolean
): Promise<FetchTweetsResponse>;
```
#### Parameters
| Parameter | Type | Default | Description |
| ---------------- | -------------- | ------------ | --------------------------------------- |
| `username` | `string` | **required** | Twitter username (without @) |
| `maxPages` | `number` | `3` | Maximum number of pages to fetch |
| `useProxies` | `boolean` | `false` | Enable proxy usage |
| `proxyOptions` | `ProxyOptions` | `undefined` | Proxy configuration options |
| `useConcurrency` | `boolean` | `false` | Enable high-performance sequential mode |
## 🌐 Proxy Management
### 1. Direct Operation (Default)
```typescript
const result = await fetchTweets("username", 3, false);
```
### 2. Custom Proxy List
```typescript
import { fetchTweets, ProxyOptions } from "nitter-scraper-v2";
const proxyOptions: ProxyOptions = {
proxyList: [
"proxy1.example.com:8080:user1:pass1",
"proxy2.example.com:8080:user2:pass2",
"proxy3.example.com:8080:user3:pass3",
],
};
const result = await fetchTweets("username", 3, true, proxyOptions);
```
### 3. Remote Proxy Configuration
```typescript
const proxyOptions: ProxyOptions = {
proxyUrl: "https://your-server.com/proxies.txt",
};
const result = await fetchTweets("username", 3, true, proxyOptions);
```
## 📝 TypeScript Definitions
### FetchTweetsResponse
```typescript
interface FetchTweetsResponse {
userProfile: UserProfile | null; // Complete user profile information
tweets: Tweet[]; // Array of extracted tweets
}
```
### UserProfile
```typescript
interface UserProfile {
username: string; // Username (@username)
fullname: string; // Full display name
description: string; // Profile bio/description
isVerified: boolean; // Verification status
verificationType: string | null; // Verification type: "business", "blue", "verified"
avatarUrl: string | null; // Profile avatar URL
bannerUrl: string | null; // Profile banner URL
stats: UserStats; // Profile statistics
joinDate: string | null; // Account creation date
location: string | null; // User location
website: string | null; // Website URL
}
```
### UserStats
```typescript
interface UserStats {
tweets: number; // Total number of tweets
following: number; // Number of accounts following
followers: number; // Number of followers
likes: number; // Total likes given
}
```
### Tweet
```typescript
interface Tweet {
id: string; // Unique tweet identifier
text: string; // Tweet content
username: string; // Author username
fullname: string; // Author display name
isVerified: boolean; // Author verification status
verificationType: string | null; // Verification type
created_at: string; // Creation timestamp (ISO)
timestamp: number | null; // Unix timestamp (milliseconds)
imageTweet: string[]; // Attached image URLs
videoTweet: string[]; // Attached video URLs (legacy)
videos: VideoInfo[]; // Detailed video information
stats: TweetStats; // Engagement statistics
avatarUrl: string | null; // Author avatar URL
cards: Card[]; // Preview cards
originalUrl: string; // Original Twitter/X URL
}
```
### TweetStats
```typescript
interface TweetStats {
comments: number; // Number of comments
retweets: number; // Number of retweets
quotes: number; // Number of quote tweets
likes: number; // Number of likes
views: number; // Number of views
}
```
### VideoInfo
```typescript
interface VideoInfo {
posterUrl: string | null; // Video thumbnail URL
videoUrl: string | null; // Video file URL
}
```
## 💡 Advanced Usage Examples
### Complete Account Analysis
```typescript
async function analyzeAccount(username: string) {
const result = await fetchTweets(username, 5, false, undefined, true);
if (result.tweets.length === 0) {
console.log("No tweets found");
return;
}
// Profile analysis
if (result.userProfile) {
const profile = result.userProfile;
console.log(`=== PROFILE ANALYSIS ===`);
console.log(`Account: ${profile.fullname} (@${profile.username})`);
console.log(`Description: ${profile.description}`);
console.log(
`Verified: ${
profile.isVerified ? `Yes (${profile.verificationType})` : "No"
}`
);
console.log(`Joined: ${profile.joinDate}`);
console.log(`Location: ${profile.location}`);
console.log(`Website: ${profile.website}`);
console.log(`\n=== PROFILE STATISTICS ===`);
console.log(`Total tweets: ${profile.stats.tweets.toLocaleString()}`);
console.log(`Following: ${profile.stats.following.toLocaleString()}`);
console.log(`Followers: ${profile.stats.followers.toLocaleString()}`);
console.log(`Likes given: ${profile.stats.likes.toLocaleString()}`);
// Calculate influence metrics
const ratio = profile.stats.followers / profile.stats.following;
console.log(`Influence ratio: ${ratio.toFixed(2)}:1`);
}
// Tweet analysis
const tweets = result.tweets;
console.log(`\n=== TWEET ANALYSIS (${tweets.length} recent tweets) ===`);
// Calculate engagement statistics
const totalLikes = tweets.reduce((sum, tweet) => sum + tweet.stats.likes, 0);
const totalRetweets = tweets.reduce(
(sum, tweet) => sum + tweet.stats.retweets,
0
);
const totalComments = tweets.reduce(
(sum, tweet) => sum + tweet.stats.comments,
0
);
const avgLikes = Math.round(totalLikes / tweets.length);
const avgRetweets = Math.round(totalRetweets / tweets.length);
const avgComments = Math.round(totalComments / tweets.length);
console.log(`Average engagement per tweet:`);
console.log(` - Likes: ${avgLikes}`);
console.log(` - Retweets: ${avgRetweets}`);
console.log(` - Comments: ${avgComments}`);
// Media usage analysis
const tweetsWithImages = tweets.filter((t) => t.imageTweet.length > 0).length;
const tweetsWithVideos = tweets.filter((t) => t.videos.length > 0).length;
const tweetsWithCards = tweets.filter((t) => t.cards.length > 0).length;
console.log(`\nMedia usage:`);
console.log(
` - Images: ${tweetsWithImages}/${tweets.length} tweets (${Math.round(
(tweetsWithImages / tweets.length) * 100
)}%)`
);
console.log(
` - Videos: ${tweetsWithVideos}/${tweets.length} tweets (${Math.round(
(tweetsWithVideos / tweets.length) * 100
)}%)`
);
console.log(
` - Cards: ${tweetsWithCards}/${tweets.length} tweets (${Math.round(
(tweetsWithCards / tweets.length) * 100
)}%)`
);
}
```
### Performance Comparison
```typescript
async function comparePerformance(username: string) {
console.log("Testing sequential mode...");
const startSequential = Date.now();
const resultSequential = await fetchTweets(
username,
3,
false,
undefined,
false
);
const timeSequential = Date.now() - startSequential;
console.log("Testing concurrent mode...");
const startConcurrent = Date.now();
const resultConcurrent = await fetchTweets(
username,
3,
false,
undefined,
true
);
const timeConcurrent = Date.now() - startConcurrent;
console.log(
`Sequential: ${resultSequential.tweets.length} tweets in ${timeSequential}ms`
);
console.log(
`Concurrent: ${resultConcurrent.tweets.length} tweets in ${timeConcurrent}ms`
);
console.log(
`Performance improvement: ${Math.round(
(timeSequential / timeConcurrent) * 100
)}%`
);
}
```
### Data Export
```typescript
import * as fs from "fs";
async function exportCompleteAnalysis(username: string, filename: string) {
const result = await fetchTweets(username, 10, false, undefined, true);
const exportData = {
metadata: {
username,
exportedAt: new Date().toISOString(),
version: "nitter-scraper-v2",
repository: "https://github.com/FaresSofiane/nitter-scraper-v2",
},
userProfile: result.userProfile,
statistics: {
totalTweets: result.tweets.length,
totalLikes: result.tweets.reduce((sum, t) => sum + t.stats.likes, 0),
totalRetweets: result.tweets.reduce(
(sum, t) => sum + t.stats.retweets,
0
),
averageEngagement:
result.tweets.reduce(
(sum, t) => sum + t.stats.likes + t.stats.retweets,
0
) / result.tweets.length,
},
tweets: result.tweets,
};
fs.writeFileSync(filename, JSON.stringify(exportData, null, 2));
console.log(`Analysis exported to ${filename}`);
}
```
## ⚡ Performance & Optimization
### Performance Modes
| Mode | Use Case | Speed | Risk Level |
| ---------- | -------------------- | ------ | ---------- |
| Sequential | Production stable | Normal | Low |
| Concurrent | Fast data collection | High | Medium |
### Best Practices
- **Concurrent mode**: Use for rapid data collection (1-10 pages)
- **Proxy rotation**: Essential for large-scale operations
- **Rate limiting**: Respect Nitter instance limitations
- **Error handling**: Implement retry logic for production use
## 🛡️ Security & Compliance
### Proxy Security
```typescript
// ✅ Recommended: Authenticated proxies
const secureProxyOptions: ProxyOptions = {
proxyList: ["premium-proxy.com:8080:username:password"],
};
// ❌ Avoid: Public proxies without authentication
const unsecureProxyOptions: ProxyOptions = {
proxyList: ["free-proxy.com:8080::"],
};
```
### Responsible Usage
```typescript
// ✅ Good: Reasonable limits
const result = await fetchTweets("username", 5);
// ❌ Avoid: Excessive requests
const result = await fetchTweets("username", 100);
```
## 🔄 Migration Guide
### From Original nitter-scraper
This package is a feature-rich fork of the original [nitter-scraper](https://www.npmjs.com/package/nitter-scraper) by [@wslyvh](https://github.com/wslyvh). While maintaining backward compatibility, it adds significant enhancements:
```typescript
// Original nitter-scraper API
import { fetchTweets } from "nitter-scraper";
const tweets = await fetchTweets("username", 3);
// nitter-scraper-v2 API (backward compatible)
import { fetchTweets } from "nitter-scraper-v2";
const result = await fetchTweets("username", 3);
const tweets = result.tweets; // Access tweets (same structure)
const profile = result.userProfile; // NEW: Access user profile
```
### Enhanced Features vs Original
| Feature | Original | nitter-scraper-v2 |
| ----------------------- | -------- | ----------------- |
| Basic tweet extraction | ✅ | ✅ |
| User profile extraction | ❌ | ✅ |
| Profile statistics | ❌ | ✅ |
| Banner images | ❌ | ✅ |
| Tweet statistics | ❌ | ✅ |
| Video information | ❌ | ✅ |
| Verification details | ❌ | ✅ |
| Proxy support | ❌ | ✅ |
| Concurrent mode | ❌ | ✅ |
### New Features Available
- `result.userProfile` - Complete user profile with statistics and banner
- `tweet.stats` - Detailed engagement metrics (likes, retweets, comments, views)
- `tweet.videos` - Enhanced video information with thumbnails
- `tweet.verificationType` - Detailed verification status ("business", "blue", "verified")
- Advanced proxy management with automatic failover
- High-performance concurrent processing mode
</details>
---
# Français
<details>
<summary><b>📖 Cliquez pour développer la documentation française</b></summary>
Un package TypeScript complet pour extraire les tweets et profils utilisateur depuis Nitter sans authentification. Ce fork amélioré du package original [nitter-scraper](https://www.npmjs.com/package/nitter-scraper) offre une gestion sophistiquée des proxies, une extraction complète des médias, des statistiques de profil utilisateur, et un traitement concurrent haute performance.
## ✨ Fonctionnalités principales
- 🚀 **Scraping sans authentification** - Aucune clé API Twitter requise
- 👤 **Profils utilisateur complets** - Extraction des informations, statistiques et bannières
- 🔄 **Pagination intelligente** - Traitement automatique multi-pages avec gestion des curseurs
- 🛡️ **Support proxy avancé** - Modes multiples avec basculement automatique
- 📷 **Extraction média complète** - Images, vidéos et cartes de prévisualisation
- 📊 **Statistiques détaillées** - Métriques d'engagement et statistiques de profil
- ⚡ **Modes haute performance** - Traitement concurrent pour collecte rapide
- 🔧 **Support TypeScript complet** - Définitions de types et IntelliSense
- 🛠️ **Prêt pour l'entreprise** - Gestion d'erreurs, logique de retry et stabilité
## 📦 Installation
```bash
# Installation comme librairie
npm install nitter-scraper-v2
# Ou installation globale pour usage CLI
npm install -g nitter-scraper-v2
```
## 🚀 Utilisation rapide
### Utilisation basique avec profil utilisateur
```typescript
import { fetchTweets } from "nitter-scraper-v2";
async function main() {
// Récupération des tweets et informations de profil
const result = await fetchTweets("elonmusk", 3);
// Affichage des informations du profil utilisateur
if (result.userProfile) {
const profile = result.userProfile;
console.log(`=== PROFIL UTILISATEUR ===`);
console.log(`Nom: ${profile.fullname} (@${profile.username})`);
console.log(`Description: ${profile.description}`);
console.log(
`Vérifié: ${
profile.isVerified ? `Oui (${profile.verificationType})` : "Non"
}`
);
console.log(`Avatar: ${profile.avatarUrl}`);
console.log(`Bannière: ${profile.bannerUrl}`);
console.log(`Inscription: ${profile.joinDate}`);
console.log(`Localisation: ${profile.location}`);
console.log(`Site web: ${profile.website}`);
console.log(`\n=== STATISTIQUES DU PROFIL ===`);
console.log(`Tweets: ${profile.stats.tweets.toLocaleString()}`);
console.log(`Abonnements: ${profile.stats.following.toLocaleString()}`);
console.log(`Abonnés: ${profile.stats.followers.toLocaleString()}`);
console.log(`Likes: ${profile.stats.likes.toLocaleString()}`);
}
// Affichage des tweets
console.log(`\n=== TWEETS (${result.tweets.length}) ===`);
result.tweets.forEach((tweet) => {
console.log(`${tweet.fullname} (@${tweet.username}): ${tweet.text}`);
console.log(
`📊 ${tweet.stats.likes} likes, ${tweet.stats.retweets} retweets`
);
if (tweet.videos.length > 0) {
console.log(`🎥 Vidéos: ${tweet.videos.length}`);
}
});
}
main().catch(console.error);
```
### Mode concurrent haute performance
```typescript
import { fetchTweets } from "nitter-scraper-v2";
async function scrapingRapide() {
// Activation du mode concurrent pour un traitement plus rapide
const result = await fetchTweets(
"username",
5, // maxPages
false, // useProxies
undefined, // proxyOptions
true // useConcurrency
);
console.log(`Traité ${result.tweets.length} tweets efficacement`);
console.log(
`Utilisateur: ${result.userProfile?.fullname} avec ${result.userProfile?.stats.followers} abonnés`
);
}
```
## 📚 Référence API complète
### Fonction fetchTweets
```typescript
function fetchTweets(
username: string,
maxPages?: number,
useProxies?: boolean,
proxyOptions?: ProxyOptions,
useConcurrency?: boolean
): Promise<FetchTweetsResponse>;
```
#### Paramètres
| Paramètre | Type | Défaut | Description |
| ---------------- | -------------- | ----------- | -------------------------------------------- |
| `username` | `string` | **requis** | Nom d'utilisateur Twitter (sans @) |
| `maxPages` | `number` | `3` | Nombre maximum de pages à récupérer |
| `useProxies` | `boolean` | `false` | Activer l'utilisation des proxies |
| `proxyOptions` | `ProxyOptions` | `undefined` | Options de configuration des proxies |
| `useConcurrency` | `boolean` | `false` | Activer le mode séquentiel haute performance |
## 🌐 Gestion des proxies
### 1. Fonctionnement direct (par défaut)
```typescript
const result = await fetchTweets("username", 3, false);
```
### 2. Liste de proxies personnalisée
```typescript
import { fetchTweets, ProxyOptions } from "nitter-scraper-v2";
const proxyOptions: ProxyOptions = {
proxyList: [
"proxy1.example.com:8080:user1:pass1",
"proxy2.example.com:8080:user2:pass2",
"proxy3.example.com:8080:user3:pass3",
],
};
const result = await fetchTweets("username", 3, true, proxyOptions);
```
### 3. Configuration proxy distante
```typescript
const proxyOptions: ProxyOptions = {
proxyUrl: "https://votre-serveur.com/proxies.txt",
};
const result = await fetchTweets("username", 3, true, proxyOptions);
```
## 📝 Définitions TypeScript
### FetchTweetsResponse
```typescript
interface FetchTweetsResponse {
userProfile: UserProfile | null; // Informations complètes du profil utilisateur
tweets: Tweet[]; // Tableau des tweets extraits
}
```
### UserProfile
```typescript
interface UserProfile {
username: string; // Nom d'utilisateur (@username)
fullname: string; // Nom complet affiché
description: string; // Bio/description du profil
isVerified: boolean; // Statut de vérification
verificationType: string | null; // Type de vérification: "business", "blue", "verified"
avatarUrl: string | null; // URL de l'avatar du profil
bannerUrl: string | null; // URL de la bannière du profil
stats: UserStats; // Statistiques du profil
joinDate: string | null; // Date de création du compte
location: string | null; // Localisation de l'utilisateur
website: string | null; // URL du site web
}
```
### UserStats
```typescript
interface UserStats {
tweets: number; // Nombre total de tweets
following: number; // Number of accounts following
followers: number; // Nombre d'abonnés
likes: number; // Total likes given
}
```
## 💡 Exemples d'utilisation avancés
### Analyse complète de compte
```typescript
async function analyserCompte(username: string) {
const result = await fetchTweets(username, 5, false, undefined, true);
if (result.tweets.length === 0) {
console.log("Aucun tweet trouvé");
return;
}
// Analyse du profil
if (result.userProfile) {
const profile = result.userProfile;
console.log(`=== ANALYSE DU PROFIL ===`);
console.log(`Compte: ${profile.fullname} (@${profile.username})`);
console.log(`Description: ${profile.description}`);
console.log(
`Vérifié: ${
profile.isVerified ? `Oui (${profile.verificationType})` : "Non"
}`
);
console.log(`Inscription: ${profile.joinDate}`);
console.log(`Localisation: ${profile.location}`);
console.log(`Site web: ${profile.website}`);
console.log(`\n=== STATISTIQUES DU PROFIL ===`);
console.log(`Total tweets: ${profile.stats.tweets.toLocaleString()}`);
console.log(`Abonnements: ${profile.stats.following.toLocaleString()}`);
console.log(`Abonnés: ${profile.stats.followers.toLocaleString()}`);
console.log(`Likes donnés: ${profile.stats.likes.toLocaleString()}`);
// Calcul des métriques d'influence
const ratio = profile.stats.followers / profile.stats.following;
console.log(`Ratio d'influence: ${ratio.toFixed(2)}:1`);
}
// Analyse des tweets
const tweets = result.tweets;
console.log(`\n=== ANALYSE DES TWEETS (${tweets.length} tweets récents) ===`);
// Calcul des statistiques d'engagement
const totalLikes = tweets.reduce((sum, tweet) => sum + tweet.stats.likes, 0);
const totalRetweets = tweets.reduce(
(sum, tweet) => sum + tweet.stats.retweets,
0
);
const totalComments = tweets.reduce(
(sum, tweet) => sum + tweet.stats.comments,
0
);
const avgLikes = Math.round(totalLikes / tweets.length);
const avgRetweets = Math.round(totalRetweets / tweets.length);
const avgComments = Math.round(totalComments / tweets.length);
console.log(`Engagement moyen par tweet:`);
console.log(` - Likes: ${avgLikes}`);
console.log(` - Retweets: ${avgRetweets}`);
console.log(` - Commentaires: ${avgComments}`);
// Analyse de l'utilisation des médias
const tweetsWithImages = tweets.filter((t) => t.imageTweet.length > 0).length;
const tweetsWithVideos = tweets.filter((t) => t.videos.length > 0).length;
const tweetsWithCards = tweets.filter((t) => t.cards.length > 0).length;
console.log(`\nUtilisation des médias:`);
console.log(
` - Images: ${tweetsWithImages}/${tweets.length} tweets (${Math.round(
(tweetsWithImages / tweets.length) * 100
)}%)`
);
console.log(
` - Vidéos: ${tweetsWithVideos}/${tweets.length} tweets (${Math.round(
(tweetsWithVideos / tweets.length) * 100
)}%)`
);
console.log(
` - Cartes: ${tweetsWithCards}/${tweets.length} tweets (${Math.round(
(tweetsWithCards / tweets.length) * 100
)}%)`
);
}
```
## ⚡ Performance et optimisation
### Modes de performance
| Mode | Cas d'usage | Vitesse | Niveau de risque |
| ---------- | ----------------- | ------- | ---------------- |
| Séquentiel | Production stable | Normal | Faible |
| Concurrent | Collecte rapide | Élevée | Moyen |
### Bonnes pratiques
- **Mode concurrent**: Utiliser pour la collecte rapide (1-10 pages)
- **Rotation de proxies**: Essentiel pour les opérations à grande échelle
- **Limitation de débit**: Respecter les limites des instances Nitter
- **Gestion d'erreurs**: Implémenter une logique de retry pour la production
</details>
---
## 📄 License
MIT License - see [LICENSE](LICENSE) file for details.
## 🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the project
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request
## 🐛 Support
For bugs and feature requests, please open an issue on [GitHub](https://github.com/FaresSofiane/nitter-scraper-v2/issues).
## 👨💻 Author
**Fares Sofiane**
- GitHub: [@FaresSofiane](https://github.com/FaresSofiane)
- Repository: [nitter-scraper-v2](https://github.com/FaresSofiane/nitter-scraper-v2)
---
<div align="center">
**Built with ❤️ by [Fares Sofiane](https://github.com/FaresSofiane)**
[⭐ Star this repository](https://github.com/FaresSofiane/nitter-scraper-v2) if you find it useful!
</div>