3 @copyright: (c) 2005 by Szoftver Messias Bt.
6 Objects of the MozillaEmulator class can emulate a browser that is capable of:
10 - configurable user agent string
12 - multipart POST (send files)
13 - receive content into file
16 I have seen many requests on the python mailing list about how to emulate a browser. I'm using this class for years now, without any problems. This is how you can use it:
19 2. Install and open the livehttpheaders plugin
20 3. Use the website manually with firefox
21 4. Check the GET and POST requests in the livehttpheaders capture window
22 5. Create an instance of the above class and send the same GET and POST requests to the server.
26 - For testing, use a MozillaCacher instance - this will cache all pages and make testing quicker
27 - You can change user agent string in the build_opened method
28 - The "encode_multipart_formdata" function can be used alone to create POST data from a list of field values and files
32 - should have a method to save/load cookies
35 #from __future__ import with_statement
44 class MozillaEmulator(object):
46 def __init__(self,cacher={},trycount=0):
47 """Create a new MozillaEmulator object.
49 @param cacher: A dictionary like object, that can cache search results on a storage device.
50 You can use a simple dictionary here, but it is not recommended.
51 You can also put None here to disable caching completely.
52 @param trycount: The download() method will retry the operation if it fails. You can specify -1 for infinite retrying.
53 A value of 0 means no retrying. A value of 1 means one retry. etc."""
55 self.cookies = cookielib.LWPCookieJar()
57 self.trycount = trycount
59 def build_opener(self,url,postdata=None,extraheaders={},forbid_redirect=False):
61 'Accept':'text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png',
62 'Accept-Language':'en,en-us;q=0.5',
63 # 'Accept-Encoding': 'gzip, deflate',
64 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
65 # 'Keep-Alive': '300',
66 # 'Connection': 'keep-alive',
67 # 'Cache-Control': 'max-age=0',
69 for key,value in extraheaders.iteritems():
70 txheaders[key] = value
71 req = urllib2.Request(url, postdata, txheaders)
72 self.cookies.add_cookie_header(req)
74 redirector = HTTPNoRedirector()
76 redirector = urllib2.HTTPRedirectHandler()
78 http_handler = urllib2.HTTPHandler(debuglevel=self.debug)
79 https_handler = urllib2.HTTPSHandler(debuglevel=self.debug)
81 u = urllib2.build_opener(http_handler,https_handler,urllib2.HTTPCookieProcessor(self.cookies),redirector)
82 u.addheaders = [('User-Agent','Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.8) Gecko/20050511 Firefox/1.0.4')]
83 if not postdata is None:
84 req.add_data(postdata)
87 def download(self,url,postdata=None,extraheaders={},forbid_redirect=False,
88 trycount=None,fd=None,onprogress=None,only_head=False):
89 """Download an URL with GET or POST methods.
91 @param postdata: It can be a string that will be POST-ed to the URL.
92 When None is given, the method will be GET instead.
93 @param extraheaders: You can add/modify HTTP headers with a dict here.
94 @param forbid_redirect: Set this flag if you do not want to handle
95 HTTP 301 and 302 redirects.
96 @param trycount: Specify the maximum number of retries here.
97 0 means no retry on error. Using -1 means infinite retring.
98 None means the default value (that is self.trycount).
99 @param fd: You can pass a file descriptor here. In this case,
100 the data will be written into the file. Please note that
101 when you save the raw data into a file then it won't be cached.
102 @param onprogress: A function that has two parameters:
103 the size of the resource and the downloaded size. This will be
104 called for each 1KB chunk. (If the HTTP header does not contain
105 the content-length field, then the size parameter will be zero!)
106 @param only_head: Create the openerdirector and return it. In other
107 words, this will not retrieve any content except HTTP headers.
109 @return: The raw HTML page data, unless fd was specified. When fd
110 was given, the return value is undefined.
113 trycount = self.trycount
117 req,u = self.build_opener(url,postdata,extraheaders,forbid_redirect)
118 openerdirector = u.open(req)
120 print req.get_method(),url
121 print openerdirector.code,openerdirector.msg
122 print openerdirector.headers
123 self.cookies.extract_cookies(openerdirector,req)
125 return openerdirector
126 #if openerdirector.headers.has_key('content-length'):
127 # length = long(openerdirector.headers['content-length'])
133 # data = openerdirector.read(1024)
134 # dlength += len(data)
137 # onprogress(length,dlength)
143 # newdata = openerdirector.read(1024)
144 # dlength += len(newdata)
147 # onprogress(length,dlength)
150 # #data = openerdirector.read()
151 # if not (self.cacher is None):
152 # self.cacher[key] = data
154 # d2= GzipFile(fileobj=cStringIO.StringIO(data)).read()
158 return opendirector.read()
159 except urllib2.URLError:
161 if (trycount > -1) and (trycount < cnt):
165 print "MozillaEmulator: urllib2.URLError, retryting ",cnt
167 # def post_multipart(self,url,fields, files, forbid_redirect=True):
168 # """Post fields and files to an http host as multipart/form-data.
169 # fields is a sequence of (name, value) elements for regular form fields.
170 # files is a sequence of (name, filename, value) elements for data to be uploaded as files
171 # Return the server's response page.
173 # content_type, post_data = encode_multipart_formdata(fields, files)
174 # result = self.download(url,post_data, {
175 # 'Content-Type': content_type,
176 # 'Content-Length': str(len(post_data))
178 # forbid_redirect=forbid_redirect
183 class HTTPNoRedirector(urllib2.HTTPRedirectHandler):
184 """This is a custom http redirect handler that FORBIDS redirection."""
186 def http_error_302(self, req, fp, code, msg, headers):
187 e = urllib2.HTTPError(req.get_full_url(), code, msg, headers, fp)
188 if e.code in (301,302):
189 if 'location' in headers:
190 newurl = headers.getheaders('location')[0]
191 elif 'uri' in headers:
192 newurl = headers.getheaders('uri')[0]
197 #def encode_multipart_formdata(fields, files):
199 # fields is a sequence of (name, value) elements for regular form fields.
200 # files is a sequence of (name, filename, value) elements for data to be uploaded as files
201 # Return (content_type, body) ready for httplib.HTTP instance
203 # BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
206 # for (key, value) in fields:
207 # L.append('--' + BOUNDARY)
208 # L.append('Content-Disposition: form-data; name="%s"' % key)
211 # for (key, filename, value) in files:
212 # L.append('--' + BOUNDARY)
213 # L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
214 # L.append('Content-Type: %s' % get_content_type(filename))
217 # L.append('--' + BOUNDARY + '--')
219 # body = CRLF.join(L)
220 # content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
221 # return content_type, body
224 #def get_content_type(filename):
225 # return mimetypes.guess_type(filename)[0] or 'application/octet-stream'