Continuing or stopping after an error is not a general preference. It depends on what broke and whether the remaining inputs still have clear boundaries. Repairing three URL-handling points in a blog collector let me follow that decision through to the files the program actually saves.
How far should one failure propagate?
If a body remains readable but one image address is malformed, should saving the entire post fail? Ignoring every error and reporting a complete download would be equally misleading. Following a URL-parsing error in the work records, I defined both the work that could continue and the failure that still needed to remain visible.
The collector handled addresses at three different boundaries: post identity, image candidates and links inside the body. Synthetic inputs with an unclosed host bracket raised ValueError at all three. This reproduced the error signature in current functions; it did not retry the historical failed pages or establish that every earlier failure had the same cause.
The next candidate was existing input, not another request
One image can provide several address candidates. The existing collector gathered lazy-loading attributes, src, srcset and a JSON attribute before inspecting the resulting order. I preserved that candidate-building logic and placed exception handling around the small address-resolution boundary. An unparseable candidate no longer prevented inspection of an already available alternative.
When a malformed bracket appears in the earlier candidate but a later candidate contains a usable relative reference, that later reference can be resolved against the base. I did not delete suspicious characters or invent a replacement address. The implementation records that one candidate could not be interpreted and selects a different supplied input. The native collector still owns candidate ordering.
Keep the exception boundary small
A broad try around an entire post could hide storage-path errors and unrelated programming defects. This repair handles ValueError at the relevant address-parsing boundaries. Identity parsing returns an unknown result, image parsing moves to another candidate, and body-link cleanup removes an unusable address attribute.
The same exception therefore leads to different actions. Returning None from the identity helper does not invent a new identifier. The original caller remains responsible for handling an unidentified item. Replacing every exception with the same empty string would erase which stage lacked which information.
Two broken attributes do not mean two failed images
If two early candidates are malformed but a later one resolves, the image-address selection has recovered. Earlier candidate errors do not increase the failed-image count in that case. If no candidate can be selected and parsing errors remain, the native code counts one failed image and continues to the next image and body-saving work.
Candidate diagnostics and image accounting therefore use different units. Excluding data or blob addresses under the existing rule does not by itself create a parsing failure either. Separating “an error was encountered” from “this image remains unresolved” makes the count useful for deciding what still needs attention.
Remove an unusable destination while keeping readable text
For a malformed body link, cleanup removes only href. The link text, surrounding paragraphs and normal links remain. Discarding an entire sentence because its destination cannot be parsed would lose more information than the defect requires. An image failure must likewise be able to coexist with successful body-text saving.
This does not repair every broken-image appearance in the saved HTML or verify the business meaning of every link. The checked behavior is narrower: a bad href does not abort cleanup, and the readable characters survive. Describing what was preserved and what became unusable is more accurate than saying the failure simply disappeared.
Address selection is different from retrying a download
The reason to advance here is a parsing failure. Once the collector chooses its first interpretable HTTP-family address, a failed download does not make this repair revisit the remaining attributes. The existing download path records the failed image. Calling this “automatically trying addresses until an image downloads” would promise behavior that was not implemented.
An interpretable address is not a security approval either. Allowed hosts and ports, authentication, redirects, response limits and image content require separate decisions. This is not a complete URL trust policy and it bypasses no access restrictions. A small recoverable candidate failure and a configuration error that should stop the operation need different treatment.
Preserve supported input as well as rejecting broken input
The separate local-development task passed fourteen new URL regression groups, fifteen existing search-rank tests and twelve whole-blog tests. Eighty measured rank-merge combinations were preserved too. Relative references, valid IPv6, brackets in queries, duplicate images and user cancellation were included. Blocking bad host brackets should not erase valid brackets elsewhere.
Synthetic inputs were connected through the native image handling and cleanup into JSON, HTML and text saving. Network and image transfers used stubs; they did not verify picture quality or decoding. The whole-blog main test used AST extraction and omitted top-level startup code. The forty-one checks and restored before/after totals of twenty-seven and forty-one belong to the previous development run. I read that evidence during this writing task without rerunning the native collector.
A repair is not proof that historical failures were recovered
The development task archived and extracted the before-and-after source, compared hashes and ran the appropriate regressions from the restored copies. Source outside the intended change and existing operating material were preserved. A passing new counterexample still does not prove that earlier failed posts and images were recovered. Revisiting the old originals and current responses is a separate task.
This public material contains none of those posts’ titles, authors, addresses or images. Reproduction needed the structure of an unparseable candidate followed by a usable one, rather than customer prose. I could share the collector’s design lesson while leaving the original material and paused monitoring work intact.
The reusable example returns selection reasons rather than addresses
The existing CSV module separates file syntax from row issues, while the destination-comparison module checks an expected navigation result. Neither implements this ordered candidate loop. I therefore made a separate synthetic helper that accepts a base and at most thirty-two candidate strings, returning a selected index, parsing-error indices, exclusion reasons and an unresolved-item signal.
The example returns no original address and makes no network request. This task ran twenty tests against its source and again after ZIP extraction, and generated six synthetic records. The selector embedded in the article reads those records rather than executing Python. Those checks concern a different target from the native collector’s forty-one. A real integration still needs candidate generation, access policy, transfer and storage outcomes, and an explicit accounting unit.