簡體   English   中英

請求標頭以抓取 pypi.org

[英]Request headers to scrape pypi.org

我正在嘗試使用請求庫和漂亮的湯來截屏 PyPI 包 - 但遇到了無限期的掛起。 我能夠從許多站點檢索 html:

session = requests.Session()
session.trust_env = False
response = session.get("http://google.com")
print(response.status_code)

即不提供標題。 我從Python 中讀到 request.get 無法得到 url 的答案我可以在我的瀏覽器上打開無限期掛起可能是由不正確的標頭引起的。 因此,使用開發人員工具,我嘗試使用“Doc”過濾器從“網絡”選項卡(使用 Edge)獲取我的請求標頭到 select pypi.org響應/請求。 我只是將這些復制粘貼到傳遞給get方法的 header 變量中:

headers = {'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9',
'cookie': 'session_id=<long string>',
'dnt': '1',
'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.54'}

(並將 get 方法更改為response = session.get("http://pypi.org", headers=headers)

但我也一樣。 所以,我認為我的標題有問題,但我不確定是什么。 我知道請求Session() “處理” cookies 所以我嘗試在我的請求 header 字典中刪除cookie鍵/值對但取得了相同的結果。

我如何確定我的標頭的問題和/或為什么我當前的標頭不起作用(假設這甚至是問題所在)?

我嘗試發送一個簡單的 HTTP 請求來查看此服務器是否需要任何標頭才能正常響應。

因此,我打開了一個 TCP 套接字並連接到 Pypi 服務器,以查看在沒有框架干預的情況下服務器如何處理請求。 此外,我們將該套接字包裝在 SSL 庫中以發送加密流量 (HTTPS)

import socket
import ssl

hostname = 'pypi.org'
context = ssl.create_default_context()

payld = ("GET / HTTP/1.1\r\n"
         f"Host: {hostname}\r\n\r\n")
with socket.create_connection((hostname, 443)) as sock:
    with context.wrap_socket(sock, server_hostname=hostname) as ssock:
        text = payld
        ssock.sendall(text.encode())
        print(ssock.recv(40))

OUTPUT (只是響應的前40個字節,但是我們可以看到狀態碼,是200 OK):

b'HTTP/1.1 200 OK\r\nConnection: keep-alive\r'

結果,我們可以得出headers 沒有效果的結論。

我建議您嘗試此代碼。

  • 如果可行:升級請求庫的版本,然后重試。
  • 如果它不起作用:我猜這是 a.network 或 SSL 驗證問題。

HTTP 標頭是一個可能的問題,但可能性不大。 更可能的原因是代理/防火牆。 我將從回顧評論中我認為相關的信息開始;

  • 您正在使用一個您沒有管理員權限的系統。
  • 系統配置為使用企業代理服務器。
  • http://pypi.org在您的瀏覽器中運行。
  • http://pypi.org從您系統上的 PowerShell 工作。
  • http://pypi.org與您的 python 代碼一起掛起。
  • 您的系統正在運行 Windows。(可能無關緊要,但可能值得注意)

由於您的瀏覽器和 PowerShell 似乎都工作正常,如果您沒有更改它們的設置,您為什么要嘗試使用 python 繞過代理? (@vader 在評論中提出這個問題,我沒有看到相關回復)
如果繞過代理對您的目標很重要,請將本節跳至下一節(在單杠之后)。 如果不是,因為其他程序似乎工作正常,我建議使用系統的原始配置嘗試使用代理;

  1. 從代碼中刪除session.trust_env = False語句。
  2. 現在測試代碼。 如果有效,我們的工作就完成了。 否則,請繼續閱讀。
  3. 還原您為使其正常工作所做的所有系統更改。
  4. 重新啟動系統。
    我自己討厭有人向我提出這樣的建議,但我發現這樣做有兩個很好的理由; 第一個是 O/S 中可能有東西卡住,重新啟動會釋放它,第二個是我可能不記得我為恢復而修補的所有東西,重新啟動可能會為我完成這項工作。
  5. 再次測試。 測試腳本,並使用瀏覽器和 PowerShell(根據@yarin-007 的評論)。

如果腳本仍然掛在對 pypi 的請求上,則需要進一步分析。 為了縮小選擇范圍,我建議如下:

  1. 通過設置allow_redirects=False禁用重定向。 如果存在重定向循環, requests應該引發TooManyRedirects異常,這將有助於識別重定向目標掛起的情況。 pypi 應該將http重定向到https而不管用戶代理或大多數其他標頭,這使得請求一致、可靠,從而限制了其他可能的因素。
  2. 設置請求超時。 超時到期時引發的異常類型可以幫助確定原因。

下面的代碼提供了一個很好的例子。 對於您的代碼,不要使用端口號,默認值應該可以工作。 我明確地添加了端口號,因為每個端口號都展示了一種不同的可能情況:

#!/usr/bin/env python
import socket
import timeit
import requests

TIMEOUT = (4, 7)    # ConnectT/O (per-IP), ReadT/O

def get_url(url, timeout=TIMEOUT):
    try:
        response = requests.get(url, timeout=timeout, allow_redirects=False)
        print(f"Status code: {response.status_code}", end="")
        if response.status_code in (301, 302):
            print(f", Location: {response.headers.get('location')}", end="")
        print(".")
    except Exception as e:
        print(f"Exception caught: {e!r}")
    finally:
        print(f"Fetching url '{url}' done", end="")

def time_url(url):
    print(f"Trying url '{url}'")
    total = timeit.timeit(f"get_url('{url}')", number=1, globals=globals())
    print(f" in: {str(total)[:4]} seconds")
    print("=============")

def print_expected_conntimeout(server):
    r = socket.getaddrinfo(server, None, socket.AF_UNSPEC, socket.SOCK_STREAM)
    print(f"IP addresses of {server}:\n" + "\n".join(addr[-1][0] for addr in r))
    print(f"Got {len(r)} addresses, so expecting a a total ConnectTimeout of {len(r) * TIMEOUT[0]}")

def main():
    scheme = "http://"
    server = "pypi.org"
    uri = f"{scheme}{server}:{{port}}".format

    print_expected_conntimeout(server)
    # OK/redirect (301)
    time_url(uri(port=80))
    # READ TIMEOUT after 7s
    time_url(uri(port=8080))
    # CONNECTION TIMEOUT after 4 * ip_addresses
    time_url(uri(port=8082))
    # REJECT
    time_url('http://localhost:80')

if __name__ == "__main__":
    main()

對我來說,這輸出:

$ ./testnet.py
IP addresses of pypi.org:
151.101.128.223
151.101.0.223
151.101.64.223
151.101.192.223
Got 4 addresses, so expecting a a total ConnectTimeout of 16
Trying url 'http://pypi.org:80'
Status code: 301, Location: https://pypi.org/.
Fetching url 'http://pypi.org:80' done in: 0.66 seconds
=============
Trying url 'http://pypi.org:8080'
Exception caught: ReadTimeout(ReadTimeoutError("HTTPConnectionPool(host='pypi.org', port=8080): Read timed out. (read timeout=7)"))
Fetching url 'http://pypi.org:8080' done in: 7.21 seconds
=============
Trying url 'http://pypi.org:8082'
Exception caught: ConnectTimeout(MaxRetryError("HTTPConnectionPool(host='pypi.org', port=8082): Max retries exceeded with url: / (Caused by ConnectTimeoutError(<urllib3.connection.HTTPConnection object at 0x103ec4730>, 'Connection to pypi.org timed out. (connect timeout=4)'))"))
Fetching url 'http://pypi.org:8082' done in: 16.0 seconds
=============
Trying url 'http://localhost:80'
Exception caught: ConnectionError(MaxRetryError("HTTPConnectionPool(host='localhost', port=80): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x103ec44c0>: Failed to establish a new connection: [Errno 61] Connection refused'))"))
Fetching url 'http://localhost:80' done in: 0.00 seconds
=============

現在解釋四種情況:

  1. http://pypi.org的成功請求返回 301 重定向 - 使用 https。
    這是你應該得到的。 如果這是在添加allow_redirects=False后得到的結果,那么主要嫌疑人就是重定向鏈,我建議類似地檢查每個location標頭的值以獲取您收到的每個重定向響應,直到找到掛起的 URL。
  2. 連接到端口 8080 成功(成功的 3 次握手),但服務器沒有返回正確的響應,並“掛起”。 requests引發ReadTimeout異常。
    如果您的腳本引發此異常,則很可能您正在連接到某種無法正確中繼(或主動阻止)請求或響應的代理。 可能有一些其他的系統設置控制它而不是trust_env ,或者一些附加到網絡基礎設施的設備。
  3. 8082端口連接不成功 無法建立 3 次握手, requests引發ConnectTimeout異常。 請注意,將嘗試連接到找到的每個 IP 地址,因此 4 秒的超時將乘以總體地址數量。
    如果這是你看到的,很可能在你的機器和 pypi 之間有一些防火牆,它要么阻止你的 SYN 數據包到達它們的目的地,要么阻止 SYN+ACK 數據包從服務器返回到你的機器。
  4. 第四種情況是作為示例提供的,我相信您不會遇到這種情況,但如果您遇到這種情況,則值得解釋一下。 在這種情況下,SYN 數據包要么到達了一個不監聽所需端口的服務器(這很奇怪,可能意味着你沒有真正到達 pypi),要么防火牆拒絕了你的 SYN 數據包(而不是簡單地丟棄它).

另一件值得關注的事情是 pypi 的 IP 地址,因為它們是由提供的腳本打印的。 雖然不能保證 IPv4 地址會保留它們的分配,但在這種情況下,如果您發現它們有很大不同 - 這表明您實際上沒有連接到真正的 pypi 服務器,因此響應是不可預測的(包括掛起)。 以下是 pypi 的 IPv4 和 IPv6 地址:

pypi.org has address 151.101.0.223
pypi.org has address 151.101.64.223
pypi.org has address 151.101.128.223
pypi.org has address 151.101.192.223
pypi.org has IPv6 address 2a04:4e42::223
pypi.org has IPv6 address 2a04:4e42:200::223
pypi.org has IPv6 address 2a04:4e42:400::223
pypi.org has IPv6 address 2a04:4e42:600::223

最后,由於我們已經觸及了不同的 IP 協議版本,也有可能在啟動連接時,您的系統嘗試使用到目的地的路由錯誤的協議(例如嘗試使用 IPv6,但其中一個網關處理不當)交通)。 通常路由器會回復 ICMP 失敗消息,但我見過這種情況沒有發生(或沒有正確中繼回來)的情況。 由於路由不受我的控制,我無法確定根本原因,但強制執行特定協議為我解決了該特定問題。

希望這提供了一些好的調試向量,如果這有幫助請添加評論,因為我很好奇你找到了什么。

知道了!

我只需要在 get 方法中設置代理變量:

headers={'User-Agent': 'Chrome'}

proxies = {
  'http': 'xxxxxx:80',
  'https': 'xxxxxx:80',
}

def get_url(url):
    try:
        response = requests.get(url, timeout=10, allow_redirects=True, headers=headers, proxies=proxies)
        print(response.headers)
        print(response.text)
        print(response.history)
        print(f"Status code: {response.status_code}")
        if response.status_code in (301, 302):
            print(f", Location: {response.headers.get('location')}")

    except Exception as e:
        print(f"Exception caught: {e!r}")
    finally:
        print(f"Fetching url '{url}' done", end="")
        

url = "http://pypi.org"
get_url(url)

你確定嗎? 只有 pypi 的主頁會引發您在任何情況下都無法抓取的錯誤,您是否有防火牆或 https 或 socks 代理?

我已經為 python 3 的所有包裹取了 url,這個代碼工作得很好

import requests
from bs4 import BeautifulSoup
hdr={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0",
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
    "Accept-Language": "en-US,en;q=0.5"}
session = requests.Session()
url="https://pypi.org/search/?c=Programming+Language+%3A%3A+Python+%3A%3A+3" # All Python 3 libraries
response = session.get(url,allow_redirects=True)
print(response.status_code)

> 200

只是為了確保..讓我們抓取 package 名稱來驗證

soup=BeautifulSoup(response.content,"lxml")
pkgs=soup.findAll('span',attrs={'class':'package-snippet__name'})
for i in pkgs:
    print(i.text)
>

yingyu-yueyueyue-201812-201909
github-actions-cicd-example
xurl
unkey
fluvio
LogicCircuit
knarrow
riyu-zhuanye-kaoyan-202203-202206
permutation
aliases
sangsangjun-202011-202101
resultify
subnuker
keke-yingyu-202101-202104
xuezhaofeng-beida-jingjixue
jingtong-jiaoben-heike
mrbenn-toolbar-plugin
liuwei-yasi-pindao-201811-201908
mypy-boto3-service-quotas
trender

第 1 頁所有 python 3 個包的名稱

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM