Build an automated SEO data pipeline using Python that collects data from multiple sources, processes it, and generates automated reports.
Prerequisites
- Python 3.8+
- API access to GSC, GA4, and Ahrefs
- Basic Python and SQL knowledge
- Server or cloud function for scheduling
Step 1: Set Up Project Structure
seo-pipeline/
├── config/
│ ├── credentials.json
│ └── settings.yaml
├── src/
│ ├── extractors/
│ │ ├── gsc_extractor.py
│ │ ├── ga4_extractor.py
│ │ └── ahrefs_extractor.py
│ ├── processors/
│ │ ├── data_processor.py
│ │ └── keyword_processor.py
│ ├── reporters/
│ │ ├── weekly_report.py
│ │ └── monthly_report.py
│ └── main.py
├── data/
│ ├── raw/
│ └── processed/
└── output/
└── reports/
Step 2: Create Data Extractors
GSC Extractor
import pandas as pd
from google.oauth2 import service_account
from googleapiclient.discovery import build
class GSCExtractor:
def __init__(self, credentials_path, site_url):
self.credentials = service_account.Credentials.from_service_account_file(
credentials_path,
scopes=['https://www.googleapis.com/auth/webmasters.readonly']
)
self.service = build('searchconsole', 'v1', credentials=self.credentials)
self.site_url = site_url
def extract_performance(self, start_date, end_date, dimensions=None):
if dimensions is None:
dimensions = ['query', 'page']
request = {
'startDate': start_date,
'endDate': end_date,
'dimensions': dimensions,
'rowLimit': 25000,
}
response = self.service.searchanalytics().query(
siteUrl=self.site_url, body=request
).execute()
rows = []
for row in response.get('rows', []):
data = dict(zip(dimensions, row['keys']))
data.update({
'clicks': row['clicks'],
'impressions': row['impressions'],
'ctr': round(row['ctr'], 4),
'position': round(row['position'], 1)
})
rows.append(data)
return pd.DataFrame(rows)
GA4 Extractor
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta import RunReportRequest, DateRange, Dimension, Metric
class GA4Extractor:
def __init__(self, credentials_path, property_id):
self.client = BetaAnalyticsDataClient.from_service_account_file(credentials_path)
self.property_id = property_id
def extract_organic_traffic(self, start_date, end_date):
request = RunReportRequest(
property=f'properties/{self.property_id}',
date_ranges=[DateRange(start_date=start_date, end_date=end_date)],
dimensions=[
Dimension(name='pagePath'),
Dimension(name='deviceCategory'),
],
metrics=[
Metric(name='sessions'),
Metric(name='averageSessionDuration'),
Metric(name='conversions'),
],
dimension_filter=self._organic_filter(),
)
response = self.client.run_report(request)
rows = []
for row in response.rows:
rows.append({
'page_path': row.dimension_values[0].value,
'device': row.dimension_values[1].value,
'sessions': int(row.metric_values[0].value),
'avg_session_duration': float(row.metric_values[1].value),
'conversions': int(row.metric_values[2].value),
})
return pd.DataFrame(rows)
def _organic_filter(self):
from google.analytics.data_v1beta import FilterExpression, Filter
return FilterExpression(
filter=Filter(
field_name='medium',
string_filter=Filter.StringFilter(value='organic')
)
)
Step 3: Data Processing
class SEODataProcessor:
def __init__(self, gsc_data, ga4_data):
self.gsc_data = gsc_data
self.ga4_data = ga4_data
def find_opportunities(self):
# Keywords with high impressions but low CTR
opportunities = self.gsc_data[
(self.gsc_data['impressions'] > 100) &
(self.gsc_data['ctr'] < 0.05) &
(self.gsc_data['position'] > 5) &
(self.gsc_data['position'] < 20)
].sort_values('impressions', ascending=False)
return opportunities
def find_quick_wins(self):
# Keywords ranking 11-20 with decent volume
quick_wins = self.gsc_data[
(self.gsc_data['position'] >= 11) &
(self.gsc_data['position'] <= 20) &
(self.gsc_data['impressions'] > 50)
].sort_values('impressions', ascending=False)
return quick_wins
def calculate_content_roi(self):
# Merge GSC and GA4 data on page URL
merged = pd.merge(
self.gsc_data.groupby('page').agg({'clicks': 'sum', 'impressions': 'sum'}),
self.ga4_data.groupby('page_path').agg({'sessions': 'sum', 'conversions': 'sum'}),
left_index=True, right_index=True, how='outer'
)
return merged.fillna(0)
Step 4: Report Generation
class WeeklySEOReport:
def __init__(self, processor):
self.processor = processor
def generate(self, output_path):
report = {
'opportunities': self.processor.find_opportunities().head(20),
'quick_wins': self.processor.find_quick_wins().head(15),
'content_roi': self.processor.calculate_content_roi(),
}
# Export to Excel with multiple sheets
with pd.ExcelWriter(output_path) as writer:
report['opportunities'].to_excel(writer, sheet_name='Opportunities')
report['quick_wins'].to_excel(writer, sheet_name='Quick Wins')
report['content_roi'].to_excel(writer, sheet_name='Content ROI')
return report
Step 5: Schedule the Pipeline
# Cron job for weekly report generation
0 6 * * 1 cd /path/to/seo-pipeline && python src/main.py >> /var/log/seo-pipeline.log 2>&1
Validation Checklist
- All API credentials configured
- GSC data extraction working
- GA4 data extraction working
- Data processing logic correct
- Report generation produces valid output
- Cron job scheduled and tested
- Error handling and logging configured
- Email notification on completion