@@ -155,6 +155,12 @@ def _is_automation_challenge(exc: Exception, code: int) -> bool:
155155def _attempt (url : str , opener : Any ) -> tuple [int | None , str | None , float | None , bool ]:
156156 """One HEAD-then-GET pass.
157157
158+ HEAD is only a shortcut: a success answers the question, and a rate-limit
159+ answer (429/503) goes straight back to :func:`check_one`'s backoff. Any other
160+ status of 400 or more, or a network error, falls through to GET and GET's
161+ answer is the result. Some hosts answer HEAD with a status GET does not
162+ give: Kaggle dataset pages answer HEAD with 404 and GET with 200.
163+
158164 Returns ``(status, final_url, retry_after, automation_challenge)``. The last
159165 value is intentionally based on an explicit gateway signal, never on a bare
160166 403, so unavailable pages cannot become promotable merely by returning 403.
@@ -164,28 +170,26 @@ def _attempt(url: str, opener: Any) -> tuple[int | None, str | None, float | Non
164170 retry_after : float | None = None
165171 automation_challenge = False
166172 for method in ("HEAD" , "GET" ):
173+ retry_after , automation_challenge = None , False
167174 try :
168175 status , final = opener .open (url , method )
169- if method == "HEAD" and status in (400 , 403 , 405 , 501 ):
170- continue # server dislikes HEAD -> retry GET
171- break
172176 except Exception as exc : # HTTPError carries a code; everything else is dead
173177 code = getattr (exc , "code" , None )
174- if isinstance (code , int ):
175- status , final = code , getattr ( exc , "url" , None ) or url
176- retry_after = _retry_after_seconds ( exc )
177- automation_challenge = _is_automation_challenge (exc , code )
178- if method == "HEAD" and code in ( 400 , 403 , 405 , 501 ):
179- continue
180- break
181- status , final , automation_challenge = None , None , False
178+ if not isinstance (code , int ):
179+ status , final = None , None
180+ continue
181+ status , final = code , getattr (exc , "url" , None ) or url
182+ retry_after = _retry_after_seconds ( exc )
183+ automation_challenge = _is_automation_challenge ( exc , code )
184+ if status < 400 or status in TRANSIENT_STATUSES :
185+ break
182186 return status , final , retry_after , automation_challenge
183187
184188
185189def check_one (
186190 url : str , opener : Any , * , on_rate_limit : Callable [[str ], None ] | None = None
187191) -> CheckResult :
188- """HEAD first; fall back to GET when HEAD is rejected (405/403) or errors .
192+ """HEAD first; when HEAD fails with anything but a rate limit, GET decides .
189193
190194 A rate-limit answer (429/503) is retried with backoff — the host is telling us
191195 to wait, not that the page is gone.
0 commit comments