fix: 페이스북 릴 수집 경로 보강

This commit is contained in:
hehihoho3@gmail.com 2026-09-04 18:08:35 +09:00
parent b01039c223
commit dcc3eba2e0
2 changed files with 50 additions and 11 deletions

View File

@ -32,6 +32,8 @@ public class FacebookPageFeedClient implements SocialFeedClient {
/** Facebook가 데이터센터별로 빈 피드를 돌려주는 경우가 있어 공식 로케일 호스트를 순서대로 시도한다. */
private static final List<String> RENDERER_HOSTS = List.of(
"www.facebook.com", "ko-kr.facebook.com", "en-gb.facebook.com");
private static final long READER_RECENT_CACHE_SECONDS = 3_600;
private static final long READER_BASELINE_CACHE_SECONDS = 2_592_000;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
@ -60,12 +62,14 @@ public class FacebookPageFeedClient implements SocialFeedClient {
}
}
if (readerFallbackEnabled) {
try {
SocialFeedBatch batch = fetchViaReader(channel, false);
if (batch.videos().isEmpty()) batch = fetchViaReader(channel, true);
if (!batch.videos().isEmpty()) return batch;
} catch (IllegalStateException e) {
lastFailure = e;
for (ReaderAttempt attempt : readerAttempts(channel.getLastCollectedAt() == null)) {
try {
SocialFeedBatch batch = fetchViaReader(channel, attempt);
if (!batch.videos().isEmpty()) return batch;
lastFailure = new IllegalStateException("Facebook Reader 공개 피드에 릴이 없습니다.");
} catch (IllegalStateException e) {
lastFailure = e;
}
}
}
throw new IllegalStateException("Facebook 공개 피드를 읽지 못했습니다: "
@ -73,14 +77,15 @@ public class FacebookPageFeedClient implements SocialFeedClient {
}
/** 운영 서버 IP가 Facebook에서 차단될 때 공개 페이지를 브라우저로 읽어 주는 Reader API를 사용한다. */
private SocialFeedBatch fetchViaReader(SocialChannel channel, boolean noCache) {
String source = channel.getSourceUrl().replaceAll("/+$", "") + "/reels/";
URI uri = URI.create("https://r.jina.ai/" + source);
private SocialFeedBatch fetchViaReader(SocialChannel channel, ReaderAttempt attempt) {
URI uri = readerUri(channel.getSourceUrl(), attempt.sourceScheme());
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.USER_AGENT, "Mozilla/5.0");
headers.setAccept(List.of(MediaType.TEXT_PLAIN));
headers.setAcceptLanguageAsLocales(List.of(Locale.ENGLISH));
headers.set("X-Engine", "browser");
if (noCache) headers.set("X-No-Cache", "true");
else headers.set("X-Cache-Tolerance", "3600");
if (attempt.noCache()) headers.set("X-No-Cache", "true");
else headers.set("X-Cache-Tolerance", Long.toString(attempt.cacheToleranceSeconds()));
try {
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET,
new HttpEntity<>(headers), String.class);
@ -94,6 +99,24 @@ public class FacebookPageFeedClient implements SocialFeedClient {
}
}
static URI readerUri(String channelUrl, String sourceScheme) {
String source = channelUrl.replaceFirst("^https?", sourceScheme).replaceAll("/+$", "") + "/reels/";
return URI.create("https://r.jina.ai/" + source);
}
static List<ReaderAttempt> readerAttempts(boolean baselineCollection) {
long fallbackTolerance = baselineCollection
? READER_BASELINE_CACHE_SECONDS
: READER_RECENT_CACHE_SECONDS;
return List.of(
new ReaderAttempt("https", true, 0),
new ReaderAttempt("http", true, 0),
new ReaderAttempt("https", false, READER_RECENT_CACHE_SECONDS),
new ReaderAttempt("http", false, fallbackTolerance));
}
record ReaderAttempt(String sourceScheme, boolean noCache, long cacheToleranceSeconds) { }
private SocialFeedBatch fetchFromHost(SocialChannel channel, String host) {
try {
Map<String, Object> config = new LinkedHashMap<>();

View File

@ -2,6 +2,8 @@ package com.hlab.yanalyst.domain.social;
import org.junit.jupiter.api.Test;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
class FacebookPageFeedClientTest {
@ -50,4 +52,18 @@ class FacebookPageFeedClientTest {
.containsExactly("2234599224051571", "926151600080380");
assertThat(batch.videos().get(0).thumbnailUrl()).isEqualTo("https://cdn.example/one.jpg?x=1&y=2");
}
@Test
void retriesReaderWithBothSourceSchemesAndUsesLongCacheOnlyForBaseline() {
assertThat(FacebookPageFeedClient.readerUri("https://www.facebook.com/phutunet/", "http"))
.isEqualTo(URI.create("https://r.jina.ai/http://www.facebook.com/phutunet/reels/"));
assertThat(FacebookPageFeedClient.readerAttempts(true))
.extracting(FacebookPageFeedClient.ReaderAttempt::sourceScheme)
.containsExactly("https", "http", "https", "http");
assertThat(FacebookPageFeedClient.readerAttempts(true).get(3).cacheToleranceSeconds())
.isEqualTo(2_592_000);
assertThat(FacebookPageFeedClient.readerAttempts(false).get(3).cacheToleranceSeconds())
.isEqualTo(3_600);
}
}