Instagram-Sync reparieren: feed/user-Endpunkt als Strategie 1
web_profile_info antwortet seit kurzem mit HTTP 400 und einem
Instagram-internen Schema-Fehler ("Asset asset://laser.provider/
ig_business_category_subvertical has been deleted") — kein IP-Block.
Gleichzeitig enthält das Profil-HTML kein edge_owner_to_timeline_media
mehr, womit auch der zweite Fallback tot war und der Cache seit
20.06. nicht mehr aktualisiert wurde.
api/v1/feed/user/{username}/username/ liefert weiterhin 200 mit den
letzten Posts, nur in anderer Form (items[] mit image_versions2 statt
edges[] mit display_url). Neuer Extractor dafür, eingehängt als
Strategie 1; die bisherigen zwei bleiben als Fallback 2 und 3.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,8 +12,9 @@ declare(strict_types=1);
|
|||||||
* Cron: 17 6,18 * * * cd /pfad/zur/site && /usr/bin/php bin/instagram-sync.php >> storage/logs/instagram.log 2>&1
|
* Cron: 17 6,18 * * * cd /pfad/zur/site && /usr/bin/php bin/instagram-sync.php >> storage/logs/instagram.log 2>&1
|
||||||
*
|
*
|
||||||
* Strategie-Kette (erster Erfolg gewinnt):
|
* Strategie-Kette (erster Erfolg gewinnt):
|
||||||
* 1. web_profile_info-API (JSON, inkl. der letzten 12 Posts)
|
* 1. feed/user-API (JSON, items[] — antwortet auch dann, wenn web_profile_info 400 wirft)
|
||||||
* 2. Profil-HTML nach eingebettetem JSON parsen
|
* 2. web_profile_info-API (JSON, edges[])
|
||||||
|
* 3. Profil-HTML nach eingebettetem JSON parsen
|
||||||
*/
|
*/
|
||||||
if (PHP_SAPI !== 'cli') {
|
if (PHP_SAPI !== 'cli') {
|
||||||
exit(1);
|
exit(1);
|
||||||
@@ -89,23 +90,68 @@ $extractPosts = static function (array $data) use ($maxPosts): array {
|
|||||||
return $posts;
|
return $posts;
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- Strategie 1: web_profile_info-API ---
|
/**
|
||||||
$posts = [];
|
* Posts aus der feed/user-Struktur ziehen (items[] mit image_versions2 statt edges[]).
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
$extractFeedItems = static function (array $data) use ($maxPosts): array {
|
||||||
|
$posts = [];
|
||||||
|
foreach (array_slice($data['items'] ?? [], 0, $maxPosts) as $item) {
|
||||||
|
// Größten Bild-Kandidaten nehmen — wird unten ohnehin auf 640x800 gecroppt.
|
||||||
|
$url = '';
|
||||||
|
$bestWidth = 0;
|
||||||
|
foreach ($item['image_versions2']['candidates'] ?? [] as $candidate) {
|
||||||
|
$width = (int) ($candidate['width'] ?? 0);
|
||||||
|
if ($width > $bestWidth && !empty($candidate['url'])) {
|
||||||
|
$bestWidth = $width;
|
||||||
|
$url = (string) $candidate['url'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($item['code']) || $url === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$posts[] = [
|
||||||
|
'shortcode' => (string) $item['code'],
|
||||||
|
'caption' => trim((string) ($item['caption']['text'] ?? '')),
|
||||||
|
'taken_at' => (int) ($item['taken_at'] ?? 0),
|
||||||
|
// media_type: 1 = Bild, 2 = Video/Reel, 8 = Karussell.
|
||||||
|
'is_video' => (int) ($item['media_type'] ?? 1) === 2 || !empty($item['video_versions']),
|
||||||
|
'display_url' => $url,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $posts;
|
||||||
|
};
|
||||||
|
|
||||||
$apiHeaders = [
|
$apiHeaders = [
|
||||||
'x-ig-app-id: 936619743392459',
|
'x-ig-app-id: 936619743392459',
|
||||||
'Accept: application/json',
|
'Accept: application/json',
|
||||||
'Referer: https://www.instagram.com/' . $username . '/',
|
'Referer: https://www.instagram.com/' . $username . '/',
|
||||||
];
|
];
|
||||||
$body = $fetch("https://www.instagram.com/api/v1/users/web_profile_info/?username={$username}", $apiHeaders);
|
|
||||||
|
// --- Strategie 1: feed/user-API ---
|
||||||
|
$posts = [];
|
||||||
|
$body = $fetch("https://www.instagram.com/api/v1/feed/user/{$username}/username/?count=12", $apiHeaders);
|
||||||
if ($body !== false) {
|
if ($body !== false) {
|
||||||
$data = json_decode($body, true);
|
$data = json_decode($body, true);
|
||||||
if (is_array($data)) {
|
if (is_array($data)) {
|
||||||
$posts = $extractPosts($data);
|
$posts = $extractFeedItems($data);
|
||||||
$log('Strategie 1 (web_profile_info): ' . count($posts) . ' Posts');
|
$log('Strategie 1 (feed/user): ' . count($posts) . ' Posts');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Strategie 2: Profil-HTML nach eingebettetem JSON parsen ---
|
// --- Strategie 2: web_profile_info-API ---
|
||||||
|
if ($posts === []) {
|
||||||
|
$body = $fetch("https://www.instagram.com/api/v1/users/web_profile_info/?username={$username}", $apiHeaders);
|
||||||
|
if ($body !== false) {
|
||||||
|
$data = json_decode($body, true);
|
||||||
|
if (is_array($data)) {
|
||||||
|
$posts = $extractPosts($data);
|
||||||
|
$log('Strategie 2 (web_profile_info): ' . count($posts) . ' Posts');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Strategie 3: Profil-HTML nach eingebettetem JSON parsen ---
|
||||||
if ($posts === []) {
|
if ($posts === []) {
|
||||||
$html = $fetch("https://www.instagram.com/{$username}/");
|
$html = $fetch("https://www.instagram.com/{$username}/");
|
||||||
if ($html !== false) {
|
if ($html !== false) {
|
||||||
@@ -138,7 +184,7 @@ if ($posts === []) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$log('Strategie 2 (HTML-Parsing): ' . count($posts) . ' Posts');
|
$log('Strategie 3 (HTML-Parsing): ' . count($posts) . ' Posts');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user