HOWTO Tìm nạp tài nguyên Internet bằng gói urllib¶
- tác giả:
Giới thiệu¶
urllib.request là mô-đun Python để tìm nạp URL (Bộ định vị tài nguyên thống nhất). Nó cung cấp một giao diện rất đơn giản, dưới dạng chức năng urlopen. Điều này có khả năng tìm nạp URL bằng nhiều giao thức khác nhau. Nó cũng cung cấp một giao diện phức tạp hơn một chút để xử lý các tình huống phổ biến - như xác thực cơ bản, cookie, proxy, v.v. Chúng được cung cấp bởi các đối tượng được gọi là trình xử lý và trình mở.
urllib.request hỗ trợ tìm nạp URL cho nhiều "sơ đồ URL" (được xác định bằng chuỗi trước ":" trong URL - ví dụ: "ftp" là sơ đồ URL của "ftp://python.org/") bằng cách sử dụng các giao thức mạng liên kết của chúng (ví dụ: FTP, HTTP). Hướng dẫn này tập trung vào trường hợp phổ biến nhất, HTTP.
Đối với những tình huống đơn giản, urlopen rất dễ sử dụng. Nhưng ngay khi gặp phải lỗi hoặc những trường hợp không hề nhỏ khi mở URL HTTP, bạn sẽ cần có một số hiểu biết về Giao thức truyền siêu văn bản. Tài liệu tham khảo toàn diện và có thẩm quyền nhất về HTTP là RFC 2616. Đây là tài liệu kỹ thuật và không nhằm mục đích dễ đọc. Zz006zz này nhằm mục đích minh họa bằng cách sử dụng urllib, với đủ chi tiết về HTTP để giúp bạn thực hiện. Nó không nhằm mục đích thay thế các tài liệu urllib.request mà chỉ bổ sung cho chúng.
Đang tìm nạp URL¶
Cách đơn giản nhất để sử dụng urllib.request như sau:
nhập urllib.request
với urllib.request.urlopen('http://python.org/') làm phản hồi:
html = phản hồi.read()
Nếu bạn muốn truy xuất tài nguyên qua URL và lưu trữ nó ở một vị trí tạm thời, bạn có thể thực hiện điều đó thông qua các hàm shutil.copyfileobj() và tempfile.NamedTemporaryFile()
nhập khẩu
nhập tệp tạm thời
nhập urllib.request
với urllib.request.urlopen('http://python.org/') làm phản hồi:
với tempfile.NamedTemporaryFile(delete=False) là tmp_file:
Shutil.copyfileobj(phản hồi, tmp_file)
với open(tmp_file.name) dưới dạng html:
vượt qua
Nhiều cách sử dụng urllib sẽ đơn giản như vậy (lưu ý rằng thay vì 'http:' URL, chúng ta có thể sử dụng URL bắt đầu bằng 'ftp:', 'file:', v.v.). Tuy nhiên, mục đích của hướng dẫn này là giải thích các trường hợp phức tạp hơn, tập trung vào HTTP.
HTTP dựa trên yêu cầu và phản hồi - máy khách đưa ra yêu cầu và máy chủ gửi phản hồi. urllib.request phản ánh điều này với một đối tượng Request đại diện cho yêu cầu HTTP mà bạn đang thực hiện. Ở dạng đơn giản nhất, bạn tạo một đối tượng Yêu cầu chỉ định URL mà bạn muốn tìm nạp. Việc gọi urlopen bằng đối tượng Yêu cầu này sẽ trả về một đối tượng phản hồi cho URL được yêu cầu. Phản hồi này là một đối tượng giống như tệp, có nghĩa là bạn có thể gọi .read() trên phản hồi:
nhập urllib.request
req = urllib.request.Request('http://python.org/')
với urllib.request.urlopen(req) làm phản hồi:
the_page = phản hồi.read()
Lưu ý rằng urllib.request sử dụng cùng một giao diện Yêu cầu để xử lý tất cả các lược đồ URL. Ví dụ: bạn có thể thực hiện yêu cầu FTP như sau:
req = urllib.request.Request('ftp://example.com/')
Trong trường hợp HTTP, có hai điều bổ sung mà đối tượng Yêu cầu cho phép bạn thực hiện: Đầu tiên, bạn có thể chuyển dữ liệu để gửi đến máy chủ. Thứ hai, bạn có thể chuyển thông tin bổ sung ("siêu dữ liệu") about dữ liệu hoặc về chính yêu cầu đó tới máy chủ - thông tin này được gửi dưới dạng "tiêu đề" HTTP. Chúng ta hãy lần lượt xem xét từng điều này.
dữ liệu¶
Đôi khi bạn muốn gửi dữ liệu tới URL (thường thì URL sẽ đề cập đến tập lệnh CGI (Giao diện cổng chung) hoặc ứng dụng web khác). Với HTTP, việc này thường được thực hiện bằng cách sử dụng yêu cầu POST. Đây thường là những gì trình duyệt của bạn thực hiện khi bạn gửi biểu mẫu HTML mà bạn đã điền trên web. Không phải tất cả POST đều phải đến từ biểu mẫu: bạn có thể sử dụng POST để truyền dữ liệu tùy ý đến ứng dụng của riêng bạn. Trong trường hợp phổ biến của biểu mẫu HTML, dữ liệu cần được mã hóa theo cách tiêu chuẩn, sau đó được chuyển đến đối tượng Yêu cầu dưới dạng đối số data. Việc mã hóa được thực hiện bằng cách sử dụng một hàm từ thư viện urllib.parse.
nhập urllib.parse
nhập urllib.request
url = 'http://www.someserver.com/cgi-bin/register.cgi'
giá trị = {'name' : 'Michael Foord',
'địa điểm' : 'Northampton',
'ngôn ngữ' : 'Python' }
dữ liệu = urllib.parse.urlencode(giá trị)
data = data.encode('ascii') # data phải là byte
req = urllib.request.Request(url, dữ liệu)
với urllib.request.urlopen(req) làm phản hồi:
the_page = phản hồi.read()
Lưu ý rằng các mã hóa khác đôi khi được yêu cầu (ví dụ: để tải tệp lên từ biểu mẫu HTML - xem HTML Specification, Form Submission để biết thêm chi tiết).
Nếu bạn không chuyển đối số data, urllib sẽ sử dụng yêu cầu GET. Một điểm khác biệt giữa các yêu cầu GET và POST là các yêu cầu POST thường có "tác dụng phụ": chúng thay đổi trạng thái của hệ thống theo một cách nào đó (ví dụ: bằng cách đặt hàng trên trang web để gửi hàng trăm thư rác đóng hộp đến tận nhà bạn). Mặc dù tiêu chuẩn HTTP nêu rõ rằng POST nhằm mục đích always gây ra tác dụng phụ và GET yêu cầu never gây ra tác dụng phụ, nhưng không có gì ngăn cản yêu cầu GET gây ra tác dụng phụ cũng như yêu cầu POST không có tác dụng phụ. Dữ liệu cũng có thể được chuyển trong yêu cầu HTTP GET bằng cách mã hóa nó trong chính URL.
Việc này được thực hiện như sau:
>>> import urllib.request
>>> import urllib.parse
>>> data = {}
>>> data['name'] = 'Somebody Here'
>>> data['location'] = 'Northampton'
>>> data['language'] = 'Python'
>>> url_values = urllib.parse.urlencode(data)
>>> print(url_values) # The order may differ from below.
name=Somebody+Here&language=Python&location=Northampton
>>> url = 'http://www.example.com/example.cgi'
>>> full_url = url + '?' + url_values
>>> data = urllib.request.urlopen(full_url)
Lưu ý rằng URL đầy đủ được tạo bằng cách thêm ? vào URL, theo sau là các giá trị được mã hóa.
Tiêu đề¶
Ở đây chúng ta sẽ thảo luận về một tiêu đề HTTP cụ thể để minh họa cách thêm tiêu đề vào yêu cầu HTTP của bạn.
Một số trang web [1] không thích bị các chương trình duyệt hoặc gửi các phiên bản khác nhau tới các trình duyệt khác nhau [2]. Theo mặc định, urllib tự nhận mình là Python-urllib/x.y (trong đó x và y là số phiên bản chính và phụ của bản phát hành Python, ví dụ: Python-urllib/2.5), điều này có thể gây nhầm lẫn cho trang web hoặc đơn giản là không hoạt động. Cách trình duyệt tự nhận dạng là thông qua tiêu đề User-Agent [3]. Khi tạo một đối tượng Yêu cầu, bạn có thể chuyển một từ điển các tiêu đề vào. Ví dụ sau đưa ra yêu cầu tương tự như trên nhưng tự xác định đó là phiên bản của Internet Explorer [4].
nhập urllib.parse
nhập urllib.request
url = 'http://www.someserver.com/cgi-bin/register.cgi'
user_agent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'
giá trị = {'name': 'Michael Foord',
'vị trí': 'Northampton',
'ngôn ngữ': 'Python' }
tiêu đề = {'Tác nhân người dùng': user_agent}
dữ liệu = urllib.parse.urlencode(giá trị)
dữ liệu = data.encode('ascii')
req = urllib.request.Request(url, dữ liệu, tiêu đề)
với urllib.request.urlopen(req) làm phản hồi:
the_page = phản hồi.read()
Phản hồi cũng có hai phương pháp hữu ích. Xem phần trên info and geturl sau khi chúng ta xem điều gì sẽ xảy ra khi có sự cố xảy ra.
Xử lý ngoại lệ¶
urlopen tăng URLError khi nó không thể xử lý phản hồi (mặc dù như thường lệ với API Python, các ngoại lệ tích hợp sẵn như ValueError, TypeError, v.v. cũng có thể được tăng lên).
HTTPError là lớp con của URLError được nêu ra trong trường hợp cụ thể của URL HTTP.
Các lớp ngoại lệ được xuất từ mô-đun urllib.error.
URLLỗi¶
Thông thường, URLError xuất hiện do không có kết nối mạng (không có tuyến đến máy chủ được chỉ định) hoặc máy chủ được chỉ định không tồn tại. Trong trường hợp này, ngoại lệ được nêu ra sẽ có thuộc tính 'lý do', là một bộ chứa mã lỗi và thông báo lỗi văn bản.
ví dụ:
>>> req = urllib.request.Request('http://www.pretend_server.org')
>>> try: urllib.request.urlopen(req)
... except urllib.error.URLError as e:
... print(e.reason)
...
(4, 'getaddrinfo failed')
Lỗi HTTP¶
Mỗi phản hồi HTTP từ máy chủ đều chứa một "mã trạng thái" bằng số. Đôi khi mã trạng thái cho biết máy chủ không thể thực hiện yêu cầu. Trình xử lý mặc định sẽ xử lý một số phản hồi này cho bạn (ví dụ: nếu phản hồi là "chuyển hướng" yêu cầu khách hàng tìm nạp tài liệu từ một URL khác, urllib sẽ xử lý việc đó cho bạn). Đối với những thứ không thể xử lý, urlopen sẽ tăng HTTPError. Các lỗi điển hình bao gồm '404' (không tìm thấy trang), '403' (yêu cầu bị cấm) và '401' (yêu cầu xác thực).
Xem phần 10 của RFC 2616 để tham khảo về tất cả các mã lỗi HTTP.
Phiên bản HTTPError được nêu ra sẽ có thuộc tính 'code' số nguyên, tương ứng với lỗi do máy chủ gửi.
Mã lỗi¶
Vì trình xử lý mặc định xử lý chuyển hướng (mã trong phạm vi 300) và mã trong phạm vi 100--299 cho biết thành công nên bạn thường sẽ chỉ thấy mã lỗi trong phạm vi 400--599.
http.server.BaseHTTPRequestHandler.responses là một từ điển hữu ích về mã phản hồi hiển thị tất cả các mã phản hồi được RFC 2616 sử dụng. Một đoạn trích từ từ điển được hiển thị dưới đây
phản hồi = {
...
<HTTStatus.OK: 200>: ('OK', 'Yêu cầu đã được thực hiện, tài liệu sau'),
...
<HTTPStatus.FORBIDDEN: 403>: ('Bị cấm',
'Yêu cầu bị cấm -- việc ủy quyền sẽ '
'không giúp được gì'),
<HTTStatus.NOT_FOUND: 404>: ('Không tìm thấy',
'Không có gì khớp với URI đã cho'),
...
<HTTPStatus.IM_A_TEAPOT: 418>: ("Tôi là Ấm Trà",
'Máy chủ từ chối pha cà phê vì'
'đó là một ấm trà'),
...
<HTTPStatus.SERVICE_UNAVAILABLE: 503>: ('Dịch vụ không khả dụng',
'Máy chủ không thể xử lý'
'yêu cầu do tải cao'),
...
}
Khi xảy ra lỗi, máy chủ sẽ phản hồi bằng cách trả về mã lỗi HTTP and một trang lỗi. Bạn có thể sử dụng phiên bản HTTPError làm phản hồi trên trang được trả về. Điều này có nghĩa là ngoài thuộc tính mã, nó còn có các phương thức đọc, geturl và thông tin được mô-đun urllib.response trả về:
>>> req = urllib.request.Request('http://www.python.org/fish.html')
>>> try:
... urllib.request.urlopen(req)
... except urllib.error.HTTPError as e:
... print(e.code)
... print(e.read())
...
404
b'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n\n\n<html
...
<title>Page Not Found</title>\n
...
Gói nó lại¶
Vì vậy, nếu bạn muốn chuẩn bị cho HTTPError or URLError thì có hai cách tiếp cận cơ bản. Tôi thích cách tiếp cận thứ hai hơn.
Số 1¶
từ yêu cầu nhập urllib.request, urlopen
từ urllib.error nhập URLError, HTTPError
req = Yêu cầu(someurl)
thử:
phản hồi = urlopen(req)
ngoại trừ HTTPError là e:
print('Máy chủ không thể thực hiện yêu cầu.')
print('Mã lỗi: ', e.code)
ngoại trừ URLError là e:
print('Chúng tôi không kết nối được với máy chủ.')
print('Lý do:', e.reason)
khác:
# everything ổn đấy
Ghi chú
except HTTPError must đến trước, nếu không except URLError sẽ also bắt HTTPError.
Số 2¶
từ yêu cầu nhập urllib.request, urlopen
từ urllib.error nhập URLError
req = Yêu cầu(someurl)
thử:
phản hồi = urlopen(req)
ngoại trừ URLError là e:
nếu hasattr(e, 'lý do'):
print('Chúng tôi không kết nối được với máy chủ.')
print('Lý do:', e.reason)
Elif hasattr(e, 'code'):
print('Máy chủ không thể thực hiện yêu cầu.')
print('Mã lỗi:', e.code)
khác:
# everything ổn đấy
thông tin và geturl¶
Phản hồi được trả về bởi urlopen (hoặc phiên bản HTTPError) có hai phương thức hữu ích info() và geturl() và được xác định trong mô-đun urllib.response.
geturl - điều này trả về URL thực của trang được tìm nạp. Điều này rất hữu ích vì
urlopen(hoặc đối tượng mở được sử dụng) có thể đã theo một chuyển hướng. Zz003zz của trang được tìm nạp có thể không giống với URL được yêu cầu.info - điều này trả về một đối tượng giống như từ điển mô tả trang được tìm nạp, đặc biệt là các tiêu đề được gửi bởi máy chủ. Nó hiện là một phiên bản
http.client.HTTPMessage.
Các tiêu đề điển hình bao gồm 'Độ dài nội dung', 'Loại nội dung', v.v. Xem Quick Reference to HTTP Headers để biết danh sách hữu ích về các tiêu đề HTTP kèm theo giải thích ngắn gọn về ý nghĩa và cách sử dụng của chúng.
Dụng cụ mở và xử lý¶
Khi bạn tìm nạp URL, bạn sử dụng một công cụ mở (một ví dụ của urllib.request.OpenerDirector có thể được đặt tên gây nhầm lẫn). Thông thường chúng tôi đang sử dụng công cụ mở mặc định - thông qua urlopen - nhưng bạn có thể tạo các công cụ mở tùy chỉnh. Dụng cụ mở sử dụng trình xử lý. Mọi việc “nâng vật nặng” đều do người xử lý đảm nhiệm. Mỗi trình xử lý đều biết cách mở URL cho một lược đồ URL cụ thể (http, ftp, v.v.) hoặc cách xử lý một khía cạnh của việc mở URL, ví dụ: chuyển hướng HTTP hoặc cookie HTTP.
Bạn sẽ muốn tạo các trình mở nếu bạn muốn tìm nạp các URL đã cài đặt các trình xử lý cụ thể, chẳng hạn như để có một trình mở xử lý cookie hoặc để có một trình mở không xử lý các chuyển hướng.
Để tạo một trình mở, hãy khởi tạo một OpenerDirector, sau đó gọi .add_handler(some_handler_instance) liên tục.
Ngoài ra, bạn có thể sử dụng build_opener, đây là một chức năng tiện lợi để tạo các đối tượng mở chỉ bằng một lệnh gọi hàm. build_opener thêm một số trình xử lý theo mặc định, nhưng cung cấp một cách nhanh chóng để thêm nhiều hơn và/hoặc ghi đè các trình xử lý mặc định.
Các loại trình xử lý khác mà bạn có thể muốn có thể xử lý proxy, xác thực và các tình huống phổ biến nhưng hơi chuyên biệt khác.
install_opener có thể được sử dụng để biến đối tượng opener thành công cụ mở mặc định (toàn cầu). Điều này có nghĩa là các lệnh gọi tới urlopen sẽ sử dụng trình mở mà bạn đã cài đặt.
Các đối tượng mở có một phương thức open, có thể được gọi trực tiếp để tìm nạp các url theo cách tương tự như hàm urlopen: không cần gọi install_opener, ngoại trừ để thuận tiện.
Xác thực cơ bản¶
Để minh họa việc tạo và cài đặt trình xử lý, chúng tôi sẽ sử dụng HTTPBasicAuthHandler. Để thảo luận chi tiết hơn về chủ đề này -- bao gồm phần giải thích về cách hoạt động của Xác thực Cơ bản - hãy xem Basic Authentication Tutorial.
Khi cần xác thực, máy chủ sẽ gửi tiêu đề (cũng như mã lỗi 401) yêu cầu xác thực. Điều này chỉ định sơ đồ xác thực và một 'cõi'. Tiêu đề trông giống như: WWW-Authenticate: SCHEME realm="REALM".
ví dụ:
WWW-Authenticate: Vương quốc cơ bản="Người dùng cPanel"
Sau đó, khách hàng nên thử lại yêu cầu với tên và mật khẩu thích hợp cho lĩnh vực được đưa vào làm tiêu đề trong yêu cầu. Đây là 'xác thực cơ bản'. Để đơn giản hóa quá trình này, chúng ta có thể tạo một phiên bản HTTPBasicAuthHandler và một trình mở để sử dụng trình xử lý này.
Zz000zz sử dụng một đối tượng được gọi là trình quản lý mật khẩu để xử lý việc ánh xạ các URL và vùng tới mật khẩu và tên người dùng. Nếu bạn biết lĩnh vực này là gì (từ tiêu đề xác thực được gửi bởi máy chủ), thì bạn có thể sử dụng HTTPPasswordMgr. Thông thường người ta không quan tâm đến cảnh giới là gì. Trong trường hợp đó, sử dụng HTTPPasswordMgrWithDefaultRealm sẽ thuận tiện hơn. Điều này cho phép bạn chỉ định tên người dùng và mật khẩu mặc định cho URL. Điều này sẽ được cung cấp trong trường hợp bạn không cung cấp sự kết hợp thay thế cho một lĩnh vực cụ thể. Chúng tôi chỉ ra điều này bằng cách cung cấp None làm đối số lĩnh vực cho phương thức add_password.
URL cấp cao nhất là URL đầu tiên yêu cầu xác thực. Các URL "sâu hơn" URL bạn chuyển tới .add_password() cũng sẽ khớp.
# create một trình quản lý mật khẩu
mật khẩu_mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
# Add tên người dùng và mật khẩu.
# If chúng tôi biết vương quốc này, chúng tôi có thể sử dụng nó thay vì Không có.
top_level_url = "http://example.com/foo/"
pass_mgr.add_password(Không có, top_level_url, tên người dùng, mật khẩu)
xử lý = urllib.request.HTTPBasicAuthHandler(password_mgr)
# create "opener" (phiên bản OpenerDirector)
opener = urllib.request.build_opener(handler)
# use dụng cụ mở để lấy URL
opener.open(a_url)
# Install người mở màn.
# Now tất cả các lệnh gọi đến urllib.request.urlopen đều sử dụng công cụ mở của chúng tôi.
urllib.request.install_opener(opener)
Ghi chú
Trong ví dụ trên, chúng tôi chỉ cung cấp HTTPBasicAuthHandler cho build_opener. Theo mặc định, các trình mở có trình xử lý cho các tình huống thông thường -- ProxyHandler (nếu cài đặt proxy chẳng hạn như biến môi trường http_proxy được đặt), UnknownHandler, HTTPHandler, HTTPDefaultErrorHandler, HTTPRedirectHandler, FTPHandler, FileHandler, DataHandler, HTTPErrorProcessor.
top_level_url trên thực tế là either một URL đầy đủ (bao gồm thành phần lược đồ 'http:' và tên máy chủ cũng như số cổng tùy chọn), ví dụ: "http://example.com/" or một "quyền" (tức là tên máy chủ, tùy chọn bao gồm số cổng), ví dụ: "example.com" hoặc "example.com:8080" (ví dụ sau bao gồm số cổng). Cơ quan có thẩm quyền, nếu có, phải NOT chứa thành phần "userinfo" - ví dụ "joe:password@example.com" là không chính xác.
Proxy¶
urllib sẽ tự động phát hiện cài đặt proxy của bạn và sử dụng chúng. Việc này được thực hiện thông qua ProxyHandler, một phần của chuỗi xử lý thông thường khi phát hiện thấy cài đặt proxy. Thông thường đó là điều tốt, nhưng có những lúc nó có thể không hữu ích [5]. Một cách để làm điều này là thiết lập ProxyHandler của riêng chúng tôi mà không cần xác định proxy. Việc này được thực hiện bằng các bước tương tự như thiết lập trình xử lý Basic Authentication:
>>> proxy_support = urllib.request.ProxyHandler({})
>>> opener = urllib.request.build_opener(proxy_support)
>>> urllib.request.install_opener(opener)
Ghi chú
Hiện tại urllib.request does not hỗ trợ tìm nạp vị trí https thông qua proxy. Tuy nhiên, điều này có thể được kích hoạt bằng cách mở rộng urllib.request như trong công thức [6].
Ghi chú
HTTP_PROXY sẽ bị bỏ qua nếu biến REQUEST_METHOD được đặt; xem tài liệu trên getproxies().
Ổ cắm và lớp¶
Hỗ trợ Python để tìm nạp tài nguyên từ web được phân lớp. urllib sử dụng thư viện http.client, thư viện này sử dụng thư viện socket.
Kể từ Python 2.3, bạn có thể chỉ định thời gian ổ cắm sẽ đợi phản hồi trước khi hết thời gian chờ. Điều này có thể hữu ích trong các ứng dụng phải tìm nạp các trang web. Theo mặc định, mô-đun ổ cắm có no timeout và có thể bị treo. Hiện tại, thời gian chờ của ổ cắm không được hiển thị ở cấp độ http.client hoặc urllib.request. Tuy nhiên, bạn có thể đặt thời gian chờ mặc định trên toàn cầu cho tất cả các ổ cắm bằng cách sử dụng
ổ cắm nhập khẩu
nhập urllib.request
# timeout trong vài giây
thời gian chờ = 10
socket.setdefaulttimeout(hết thời gian chờ)
Cuộc gọi # this tới urllib.request.urlopen hiện sử dụng thời gian chờ mặc định
# we đã thiết lập trong mô-đun ổ cắm
req = urllib.request.Request('http://www.voidspace.org.uk')
phản hồi = urllib.request.urlopen(req)
Chú thích cuối trang¶
Tài liệu này đã được John Lee xem xét và sửa đổi.