Skip to content

feat: add InBerlinWohnen provider#367

Open
madsbergstroem wants to merge 4 commits into
orangecoding:masterfrom
TruffleCraft:feat/inberlinwohnen-provider
Open

feat: add InBerlinWohnen provider#367
madsbergstroem wants to merge 4 commits into
orangecoding:masterfrom
TruffleCraft:feat/inberlinwohnen-provider

Conversation

@madsbergstroem

@madsbergstroem madsbergstroem commented Jul 17, 2026

Copy link
Copy Markdown

Summary

  • add inberlinwohnen.de as a native Fredy provider
  • crawl every result page through Fredy's shared CloakBrowser instead of direct HTML fetches
  • parse structured Livewire snapshots and reject incomplete or bot-detection responses instead of treating them as empty results
  • enrich new listings from supported partner detail pages with safe fallbacks
  • scope similarity deduplication by job so one job cannot hide matching listings from another

Implementation

The provider reuses one shared browser for pagination and optional detail enrichment, preserving Fredy's proxy, timeout, and bot-detection behavior. It normalizes stable partner IDs, canonical allowlisted deeplinks, total rent, size, rooms, address, image, provider details, WBS, and availability.

fetchDetails uses explicit selectors for Berlinovo, degewo, GESOBAU, Gewobag, HOWOGE, and WBM. STADT UND LAND and any failed or unsupported detail extraction retain the structured InBerlinWohnen snapshot description.

Similarity hashes now include jobId, preserving cross-provider deduplication within a job without suppressing the same listing in another job. Cache hydration, address normalization, hard-delete eviction, and hidden-duplicate handling use the same identity.

Verification

  • npm run test:offline: 41 files, 252 tests passed, 1 live-only test skipped
  • npm test -- test/provider/inberlinwohnen.test.js --testTimeout=180000: 5 live tests passed, 13 offline-only tests skipped
  • live crawl: all 27 current result pages plus one current detail page for each of the six enrichable partner domains
  • npm run lint: passed
  • npm run format:check: passed
  • npm run build:frontend: passed

Comment thread lib/provider/inberlinwohnen.js Outdated
async function fetchPage(page) {
const pageUrl = new URL(url);
if (page > 1) pageUrl.searchParams.set('page', String(page));
const response = await fetch(pageUrl, { headers: { Accept: 'text/html' } });

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you use fetch instead of puppeteer? Fetch is known to be caught by bot detection pretty easily

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I replaced the direct listing-page fetches with the shared CloakBrowser. The provider now declares requiresBrowser, receives the job browser in getListings(url, browser), and reuses it across all result pages and detail requests, including the configured proxy and bot-detection handling. Structurally incomplete pages now fail the crawl instead of looking like an empty result.

},
normalize,
filter: applyBlacklist,
activeTester: isListingActive,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would you mind also implementing fetchDetails?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented. fetchDetails reuses the shared browser and has explicit parsers for Berlinovo, degewo, GESOBAU, Gewobag, HOWOGE, and WBM. STADT UND LAND currently exposes no useful narrative detail block, so it keeps the structured snapshot description. Unknown hosts, bot detection, and parser failures always return the original listing. The live smoke test verifies one current page for every enrichable partner domain.

const newCache = new Set();
for (const entry of allEntries) {
newCache.add(toHash(entry?.title, entry?.price, entry?.address));
newCache.add(toHash(entry?.job_id, entry?.title, entry?.price, entry?.address));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you want to accomplish here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to keep cross-provider similarity deduplication within one job while preventing one job from hiding the same listing from another job. Persistent listing uniqueness is already scoped by (job_id, hash), so the in-memory cache now follows the same boundary. I expanded the tests and fixed the related hard-delete path: hydration and eviction include job_id, addresses use the same normalization as storage, and deleting an already hidden duplicate no longer evicts the active listing cache entry.

@madsbergstroem

Copy link
Copy Markdown
Author

Addressed all three review threads in 8bc8c9c: shared CloakBrowser pagination, partner-specific fetchDetails, and fully tested job-scoped similarity caching. The new upstream workflow runs are currently waiting for maintainer approval (action_required), while the equivalent local offline, lint, format, build, and live-provider checks pass. Re-review would be appreciated.

}

if (!browser && matchedProvider.config.getListings == null) {
if (!browser && (matchedProvider.config.getListings == null || matchedProvider.config.requiresBrowser)) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? I know you're using claude.. While this is fine, you should tell it not to push in vars/methods for the sake of doing so.

please remove the requiredBrowser again from where you/claude added it. The shared browser instance should be available in your provider whether or not you make use out of it ;)

The implementation of your provider is already much more complicated than the rest.
Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants