14.11. Module urllib RevisitedThe httplib module we just met provides low-level control for HTTP clients. When dealing with items available on the Web, though, it's often easier to code downloads with Python's standard urllib module, introduced in the FTP section earlier in this chapter. Since this module is another way to talk HTTP, let's expand on its interfaces here. Recall that given a URL, urllib either downloads the requested object over the Net to a local file, or gives us a file-like object from which we can read the requested object's contents. As a result, the script in Example 14-30 does the same work as the httplib script we just wrote, but requires noticeably less code. Example 14-30. PP3E\Internet\Other\http-getfile-urllib1.py
Almost all HTTP transfer details are hidden behind the urllib interface here. This version works in almost the same way as the httplib version we wrote first, but it builds and submits an Internet URL address to get its work done (the constructed URL is printed as the script's first output line). As we saw in the FTP section of this chapter, the urllib urlopen function returns a file-like object from which we can read the remote data. But because the constructed URLs begin with "http://" here, the urllib module automatically employs the lower-level HTTP interfaces to download the requested file, not FTP: C:\...\PP3E\Internet\Other>python http-getfile-urllib1.py http://starship.python.net/index.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <META NAME="GENERATOR" CONTENT="HTMLgen"> <TITLE>Starship Python -- Python Programming Community</TITLE> <LINK REL="SHORTCUT ICON" HREF="http://starship.python.net/favicon.ico"> C:\...\PP3E\Internet\Other>python http-getfile-urllib1.py www.python.org /index http://www.python.org/index <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <?xml-stylesheet href="./css/ht2html.css" type="text/css"?> <html> <!-- THIS PAGE IS AUTOMATICALLY GENERATED. DO NOT EDIT. --> <!-- Mon Jan 16 13:02:12 2006 --> C:\...\PP3E\Internet\Other>python http-getfile-urllib1.py www.rmi.net /~lutz http://www.rmi.net/~lutz <HTML> <HEAD> <TITLE>Mark Lutz's Home Page</TITLE> </HEAD> <BODY BGCOLOR="#f1f1ff"> C:\...\PP3E\Internet\Other>python http-getfile-urllib1.py localhost /cgi-bin/languages.py?language=Java http://localhost/cgi-bin/languages.py?language=Java <TITLE>Languages</TITLE> <H1>Syntax</H1><HR> <H3>Java</H3><P><PRE> System.out.println("Hello World"); </PRE></P><BR> <HR> As before, the filename argument can name a simple file or a program invocation with optional parameters at the end, as in the last run here. If you read this output carefully, you'll notice that this script still works if you leave the "index.html" off the end of a filename (in the third command line); unlike the raw HTTP version of the preceding section, the URL-based interface is smart enough to do the right thing. 14.11.1. Other urllib InterfacesOne last mutation: the following urllib downloader script uses the slightly higher-level urlretrieve interface in that module to automatically save the downloaded file or script output to a local file on the client machine. This interface is handy if we really mean to store the fetched data (e.g., to mimic the FTP protocol). If we plan on processing the downloaded data immediately, though, this form may be less convenient than the version we just met: we need to open and read the saved file. Moreover, we need to provide an extra protocol for specifying or extracting a local filename, as in Example 14-31. Example 14-31. PP3E\Internet\Other\http-getfile-urllib2.py
Let's run this last variant from a command line. Its basic operation is the same as the last two versions: like the prior one, it builds a URL, and like both of the last two, we can list an explicit target server and file path on the command line: C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py http://starship.python.net/index.html index.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <META NAME="GENERATOR" CONTENT="HTMLgen"> <TITLE>Starship Python -- Python Programming Community</TITLE> <LINK REL="SHORTCUT ICON" HREF="http://starship.python.net/favicon.ico"> C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py www.python.org /index.html http://www.python.org/index.html index.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <?xml-stylesheet href="./css/ht2html.css" type="text/css"?> <html> <!-- THIS PAGE IS AUTOMATICALLY GENERATED. DO NOT EDIT. --> <!-- Mon Jan 16 13:02:12 2006 --> Because this version uses an urllib interface that automatically saves the downloaded data in a local file, it's similar to FTP downloads in spirit. But this script must also somehow come up with a local filename for storing the data. You can either let the script strip and use the base filename from the constructed URL, or explicitly pass a local filename as a last command-line argument. In the prior run, for instance, the downloaded web page is stored in the local file index.html in the current working directorythe base filename stripped from the URL (the script prints the URL and local filename as its first output line). In the next run, the local filename is passed explicitly as python-org-index.html: C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py www.python.org /index.html python-org-index.html http://www.python.org/index.html python-org-index.html <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd" > <?xml-stylesheet href="./css/ht2html.css" type="text/css"?> <html> <!-- THIS PAGE IS AUTOMATICALLY GENERATED. DO NOT EDIT. --> <!-- Mon Jan 16 13:02:12 2006 --> C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py www.rmi.net /~lutz/home/index.html http://www.rmi.net/~lutz/index.html index.html <HTML> <HEAD> <TITLE>Mark Lutz's Home Page</TITLE> </HEAD> <BODY BGCOLOR="#f1f1ff"> C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py www.rmi.net /~lutz/home/about-pp.html http://www.rmi.net/~lutz/about-pp.html about-pp.html <HTML> <HEAD> <TITLE>About "Programming Python"</TITLE> </HEAD> What follows is a listing showing this third version being used to trigger a remote program. As before, if you don't give the local filename explicitly, the script strips the base filename out of the filename argument. That's not always easy or appropriate for program invocationsthe filename can contain both a remote directory path at the front, and query parameters at the end for a remote program invocation. Given a script invocation URL and no explicit output filename, the script extracts the base filename in the middle by using first the standard urlparse module to pull out the file path, and then os.path.split to strip off the directory path. However, the resulting filename is a remote script's name, and it may or may not be an appropriate place to store the data locally. In the first run that follows, for example, the script's output goes in a local file called languages.py, the script name in the middle of the URL; in the second, we instead name the output CxxSyntax.html explicitly to suppress filename extraction: C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py localhost /cgi-bin/languages.py?language=Scheme http://localhost/cgi-bin/languages.py?language=Scheme languages.py <TITLE>Languages</TITLE> <H1>Syntax</H1><HR> <H3>Scheme</H3><P><PRE> (display "Hello World") (newline) </PRE></P><BR> <HR> C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py localhost /cgi-bin/languages.py?language=C++ CxxSyntax.html http://localhost/cgi-bin/languages.py?language=C++ CxxSyntax.html <TITLE>Languages</TITLE> <H1>Syntax</H1><HR> <H3>C </H3><P><PRE> Sorry--I don't know that language </PRE></P><BR> <HR> The remote script returns a not-found message when passed "C++" in the last command here. It turns out that "+" is a special character in URL strings (meaning a space), and to be robust, both of the urllib scripts we've just written should really run the filename string through something called urllib.quote, a tool that escapes special characters for transmission. We will talk about this in depth in Chapter 16, so consider this a preview for now. But to make this invocation work, we need to use special sequences in the constructed URL. Here's how to do it by hand: C:\...\PP3E\Internet\Other>python http-getfile-urllib2.py localhost /cgi-bin/languages.py?language=C%2b%2b CxxSyntax.html http://localhost/cgi-bin/languages.py?language=C%2b%2b CxxSyntax.html <TITLE>Languages</TITLE> <H1>Syntax</H1><HR> <H3>C++</H3><P><PRE> cout << "Hello World" << endl; </PRE></P><BR> <HR> The odd %2b strings in this command line are not entirely magical: the escaping required for URLs can be seen by running standard Python tools manually (this is what these scripts should do automatically to handle all possible cases well): C:\...\PP3E\Internet\Other>python >>> import urllib >>> urllib.quote('C++') 'C%2b%2b' Again, don't work too hard at understanding these last few commands; we will revisit URLs and URL escapes in Chapter 16, while exploring server-side scripting in Python. I will also explain there why the C++ result came back with other oddities like <<HTML escapes for <<, generated by the tool cgi.escape in the script on the server that produces the reply: >>> import cgi >>> cgi.escape('<<') '<<' Also in Chapter 16, we'll meet urllib support for proxies, and the support for client-side cookies in the newer urllib2 standard library module. We'll discuss the related HTTPS concept in Chapter 17HTTP transmissions over secure sockets, supported by urllib and urllib2 if SSL support is compiled into your Python. |