Advertisement
Aluf

mechanize.py - By Aluf

Apr 9th, 2015
771
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 24.98 KB | None | 0 0
  1. """Stateful programmatic WWW navigation, after Perl's WWW::Mechanize.
  2.  
  3. Copyright 2003-2006 John J. Lee <[email protected]>
  4. Copyright 2003 Andy Lester (original Perl code)
  5.  
  6. This code is free software; you can redistribute it and/or modify it
  7. under the terms of the BSD or ZPL 2.1 licenses (see the file COPYING.txt
  8. included with the distribution).
  9.  
  10. """
  11.  
  12. import copy, re, os, urllib, urllib2
  13.  
  14. from _html import DefaultFactory
  15. import _response
  16. import _request
  17. import _rfc3986
  18. import _sockettimeout
  19. import _urllib2_fork
  20. from _useragent import UserAgentBase
  21.  
  22. class BrowserStateError(Exception): pass
  23. class LinkNotFoundError(Exception): pass
  24. class FormNotFoundError(Exception): pass
  25.  
  26.  
  27. def sanepathname2url(path):
  28. urlpath = urllib.pathname2url(path)
  29. if os.name == "nt" and urlpath.startswith("///"):
  30. urlpath = urlpath[2:]
  31. # XXX don't ask me about the mac...
  32. return urlpath
  33.  
  34.  
  35. class History:
  36. """
  37.  
  38. Though this will become public, the implied interface is not yet stable.
  39.  
  40. """
  41. def __init__(self):
  42. self._history = [] # LIFO
  43. def add(self, request, response):
  44. self._history.append((request, response))
  45. def back(self, n, _response):
  46. response = _response # XXX move Browser._response into this class?
  47. while n > 0 or response is None:
  48. try:
  49. request, response = self._history.pop()
  50. except IndexError:
  51. raise BrowserStateError("already at start of history")
  52. n -= 1
  53. return request, response
  54. def clear(self):
  55. del self._history[:]
  56. def close(self):
  57. for request, response in self._history:
  58. if response is not None:
  59. response.close()
  60. del self._history[:]
  61.  
  62.  
  63. class HTTPRefererProcessor(_urllib2_fork.BaseHandler):
  64. def http_request(self, request):
  65. # See RFC 2616 14.36. The only times we know the source of the
  66. # request URI has a URI associated with it are redirect, and
  67. # Browser.click() / Browser.submit() / Browser.follow_link().
  68. # Otherwise, it's the user's job to add any Referer header before
  69. # .open()ing.
  70. if hasattr(request, "redirect_dict"):
  71. request = self.parent._add_referer_header(
  72. request, origin_request=False)
  73. return request
  74.  
  75. https_request = http_request
  76.  
  77.  
  78. class Browser(UserAgentBase):
  79. """Browser-like class with support for history, forms and links.
  80.  
  81. BrowserStateError is raised whenever the browser is in the wrong state to
  82. complete the requested operation - e.g., when .back() is called when the
  83. browser history is empty, or when .follow_link() is called when the current
  84. response does not contain HTML data.
  85.  
  86. Public attributes:
  87.  
  88. request: current request (mechanize.Request)
  89. form: currently selected form (see .select_form())
  90.  
  91. """
  92.  
  93. handler_classes = copy.copy(UserAgentBase.handler_classes)
  94. handler_classes["_referer"] = HTTPRefererProcessor
  95. default_features = copy.copy(UserAgentBase.default_features)
  96. default_features.append("_referer")
  97.  
  98. def __init__(self,
  99. factory=None,
  100. history=None,
  101. request_class=None,
  102. ):
  103. """
  104.  
  105. Only named arguments should be passed to this constructor.
  106.  
  107. factory: object implementing the mechanize.Factory interface.
  108. history: object implementing the mechanize.History interface. Note
  109. this interface is still experimental and may change in future.
  110. request_class: Request class to use. Defaults to mechanize.Request
  111.  
  112. The Factory and History objects passed in are 'owned' by the Browser,
  113. so they should not be shared across Browsers. In particular,
  114. factory.set_response() should not be called except by the owning
  115. Browser itself.
  116.  
  117. Note that the supplied factory's request_class is overridden by this
  118. constructor, to ensure only one Request class is used.
  119.  
  120. """
  121. self._handle_referer = True
  122.  
  123. if history is None:
  124. history = History()
  125. self._history = history
  126.  
  127. if request_class is None:
  128. request_class = _request.Request
  129.  
  130. if factory is None:
  131. factory = DefaultFactory()
  132. factory.set_request_class(request_class)
  133. self._factory = factory
  134. self.request_class = request_class
  135.  
  136. self.request = None
  137. self._set_response(None, False)
  138.  
  139. # do this last to avoid __getattr__ problems
  140. UserAgentBase.__init__(self)
  141.  
  142. def close(self):
  143. UserAgentBase.close(self)
  144. if self._response is not None:
  145. self._response.close()
  146. if self._history is not None:
  147. self._history.close()
  148. self._history = None
  149.  
  150. # make use after .close easy to spot
  151. self.form = None
  152. self.request = self._response = None
  153. self.request = self.response = self.set_response = None
  154. self.geturl = self.reload = self.back = None
  155. self.clear_history = self.set_cookie = self.links = self.forms = None
  156. self.viewing_html = self.encoding = self.title = None
  157. self.select_form = self.click = self.submit = self.click_link = None
  158. self.follow_link = self.find_link = None
  159.  
  160. def set_handle_referer(self, handle):
  161. """Set whether to add Referer header to each request."""
  162. self._set_handler("_referer", handle)
  163. self._handle_referer = bool(handle)
  164.  
  165. def _add_referer_header(self, request, origin_request=True):
  166. if self.request is None:
  167. return request
  168. scheme = request.get_type()
  169. original_scheme = self.request.get_type()
  170. if scheme not in ["http", "https"]:
  171. return request
  172. if not origin_request and not self.request.has_header("Referer"):
  173. return request
  174.  
  175. if (self._handle_referer and
  176. original_scheme in ["http", "https"] and
  177. not (original_scheme == "https" and scheme != "https")):
  178. # strip URL fragment (RFC 2616 14.36)
  179. parts = _rfc3986.urlsplit(self.request.get_full_url())
  180. parts = parts[:-1]+(None,)
  181. referer = _rfc3986.urlunsplit(parts)
  182. request.add_unredirected_header("Referer", referer)
  183. return request
  184.  
  185. def open_novisit(self, url, data=None,
  186. timeout=_sockettimeout._GLOBAL_DEFAULT_TIMEOUT):
  187. """Open a URL without visiting it.
  188.  
  189. Browser state (including request, response, history, forms and links)
  190. is left unchanged by calling this function.
  191.  
  192. The interface is the same as for .open().
  193.  
  194. This is useful for things like fetching images.
  195.  
  196. See also .retrieve().
  197.  
  198. """
  199. return self._mech_open(url, data, visit=False, timeout=timeout)
  200.  
  201. def open(self, url, data=None,
  202. timeout=_sockettimeout._GLOBAL_DEFAULT_TIMEOUT):
  203. return self._mech_open(url, data, timeout=timeout)
  204.  
  205. def _mech_open(self, url, data=None, update_history=True, visit=None,
  206. timeout=_sockettimeout._GLOBAL_DEFAULT_TIMEOUT):
  207. try:
  208. url.get_full_url
  209. except AttributeError:
  210. # string URL -- convert to absolute URL if required
  211. scheme, authority = _rfc3986.urlsplit(url)[:2]
  212. if scheme is None:
  213. # relative URL
  214. if self._response is None:
  215. raise BrowserStateError(
  216. "can't fetch relative reference: "
  217. "not viewing any document")
  218. url = _rfc3986.urljoin(self._response.geturl(), url)
  219.  
  220. request = self._request(url, data, visit, timeout)
  221. visit = request.visit
  222. if visit is None:
  223. visit = True
  224.  
  225. if visit:
  226. self._visit_request(request, update_history)
  227.  
  228. success = True
  229. try:
  230. response = UserAgentBase.open(self, request, data)
  231. except urllib2.HTTPError, error:
  232. success = False
  233. if error.fp is None: # not a response
  234. raise
  235. response = error
  236. ## except (IOError, socket.error, OSError), error:
  237. ## # Yes, urllib2 really does raise all these :-((
  238. ## # See test_urllib2.py for examples of socket.gaierror and OSError,
  239. ## # plus note that FTPHandler raises IOError.
  240. ## # XXX I don't seem to have an example of exactly socket.error being
  241. ## # raised, only socket.gaierror...
  242. ## # I don't want to start fixing these here, though, since this is a
  243. ## # subclass of OpenerDirector, and it would break old code. Even in
  244. ## # Python core, a fix would need some backwards-compat. hack to be
  245. ## # acceptable.
  246. ## raise
  247.  
  248. if visit:
  249. self._set_response(response, False)
  250. response = copy.copy(self._response)
  251. elif response is not None:
  252. response = _response.upgrade_response(response)
  253.  
  254. if not success:
  255. raise response
  256. return response
  257.  
  258. def __str__(self):
  259. text = []
  260. text.append("<%s " % self.__class__.__name__)
  261. if self._response:
  262. text.append("visiting %s" % self._response.geturl())
  263. else:
  264. text.append("(not visiting a URL)")
  265. if self.form:
  266. text.append("\n selected form:\n %s\n" % str(self.form))
  267. text.append(">")
  268. return "".join(text)
  269.  
  270. def response(self):
  271. """Return a copy of the current response.
  272.  
  273. The returned object has the same interface as the object returned by
  274. .open() (or mechanize.urlopen()).
  275.  
  276. """
  277. return copy.copy(self._response)
  278.  
  279. def open_local_file(self, filename):
  280. path = sanepathname2url(os.path.abspath(filename))
  281. url = 'file://'+path
  282. return self.open(url)
  283.  
  284. def set_response(self, response):
  285. """Replace current response with (a copy of) response.
  286.  
  287. response may be None.
  288.  
  289. This is intended mostly for HTML-preprocessing.
  290. """
  291. self._set_response(response, True)
  292.  
  293. def _set_response(self, response, close_current):
  294. # sanity check, necessary but far from sufficient
  295. if not (response is None or
  296. (hasattr(response, "info") and hasattr(response, "geturl") and
  297. hasattr(response, "read")
  298. )
  299. ):
  300. raise ValueError("not a response object")
  301.  
  302. self.form = None
  303. if response is not None:
  304. response = _response.upgrade_response(response)
  305. if close_current and self._response is not None:
  306. self._response.close()
  307. self._response = response
  308. self._factory.set_response(response)
  309.  
  310. def visit_response(self, response, request=None):
  311. """Visit the response, as if it had been .open()ed.
  312.  
  313. Unlike .set_response(), this updates history rather than replacing the
  314. current response.
  315. """
  316. if request is None:
  317. request = _request.Request(response.geturl())
  318. self._visit_request(request, True)
  319. self._set_response(response, False)
  320.  
  321. def _visit_request(self, request, update_history):
  322. if self._response is not None:
  323. self._response.close()
  324. if self.request is not None and update_history:
  325. self._history.add(self.request, self._response)
  326. self._response = None
  327. # we want self.request to be assigned even if UserAgentBase.open
  328. # fails
  329. self.request = request
  330.  
  331. def geturl(self):
  332. """Get URL of current document."""
  333. if self._response is None:
  334. raise BrowserStateError("not viewing any document")
  335. return self._response.geturl()
  336.  
  337. def reload(self):
  338. """Reload current document, and return response object."""
  339. if self.request is None:
  340. raise BrowserStateError("no URL has yet been .open()ed")
  341. if self._response is not None:
  342. self._response.close()
  343. return self._mech_open(self.request, update_history=False)
  344.  
  345. def back(self, n=1):
  346. """Go back n steps in history, and return response object.
  347.  
  348. n: go back this number of steps (default 1 step)
  349.  
  350. """
  351. if self._response is not None:
  352. self._response.close()
  353. self.request, response = self._history.back(n, self._response)
  354. self.set_response(response)
  355. if not response.read_complete:
  356. return self.reload()
  357. return copy.copy(response)
  358.  
  359. def clear_history(self):
  360. self._history.clear()
  361.  
  362. def set_cookie(self, cookie_string):
  363. """Request to set a cookie.
  364.  
  365. Note that it is NOT necessary to call this method under ordinary
  366. circumstances: cookie handling is normally entirely automatic. The
  367. intended use case is rather to simulate the setting of a cookie by
  368. client script in a web page (e.g. JavaScript). In that case, use of
  369. this method is necessary because mechanize currently does not support
  370. JavaScript, VBScript, etc.
  371.  
  372. The cookie is added in the same way as if it had arrived with the
  373. current response, as a result of the current request. This means that,
  374. for example, if it is not appropriate to set the cookie based on the
  375. current request, no cookie will be set.
  376.  
  377. The cookie will be returned automatically with subsequent responses
  378. made by the Browser instance whenever that's appropriate.
  379.  
  380. cookie_string should be a valid value of the Set-Cookie header.
  381.  
  382. For example:
  383.  
  384. browser.set_cookie(
  385. "sid=abcdef; expires=Wednesday, 09-Nov-06 23:12:40 GMT")
  386.  
  387. Currently, this method does not allow for adding RFC 2986 cookies.
  388. This limitation will be lifted if anybody requests it.
  389.  
  390. """
  391. if self._response is None:
  392. raise BrowserStateError("not viewing any document")
  393. if self.request.get_type() not in ["http", "https"]:
  394. raise BrowserStateError("can't set cookie for non-HTTP/HTTPS "
  395. "transactions")
  396. cookiejar = self._ua_handlers["_cookies"].cookiejar
  397. response = self.response() # copy
  398. headers = response.info()
  399. headers["Set-cookie"] = cookie_string
  400. cookiejar.extract_cookies(response, self.request)
  401.  
  402. def links(self, **kwds):
  403. """Return iterable over links (mechanize.Link objects)."""
  404. if not self.viewing_html():
  405. raise BrowserStateError("not viewing HTML")
  406. links = self._factory.links()
  407. if kwds:
  408. return self._filter_links(links, **kwds)
  409. else:
  410. return links
  411.  
  412. def forms(self):
  413. """Return iterable over forms.
  414.  
  415. The returned form objects implement the mechanize.HTMLForm interface.
  416.  
  417. """
  418. if not self.viewing_html():
  419. raise BrowserStateError("not viewing HTML")
  420. return self._factory.forms()
  421.  
  422. def global_form(self):
  423. """Return the global form object, or None if the factory implementation
  424. did not supply one.
  425.  
  426. The "global" form object contains all controls that are not descendants
  427. of any FORM element.
  428.  
  429. The returned form object implements the mechanize.HTMLForm interface.
  430.  
  431. This is a separate method since the global form is not regarded as part
  432. of the sequence of forms in the document -- mostly for
  433. backwards-compatibility.
  434.  
  435. """
  436. if not self.viewing_html():
  437. raise BrowserStateError("not viewing HTML")
  438. return self._factory.global_form
  439.  
  440. def viewing_html(self):
  441. """Return whether the current response contains HTML data."""
  442. if self._response is None:
  443. raise BrowserStateError("not viewing any document")
  444. return self._factory.is_html
  445.  
  446. def encoding(self):
  447. if self._response is None:
  448. raise BrowserStateError("not viewing any document")
  449. return self._factory.encoding
  450.  
  451. def title(self):
  452. r"""Return title, or None if there is no title element in the document.
  453.  
  454. Treatment of any tag children of attempts to follow Firefox and IE
  455. (currently, tags are preserved).
  456.  
  457. """
  458. if not self.viewing_html():
  459. raise BrowserStateError("not viewing HTML")
  460. return self._factory.title
  461.  
  462. def select_form(self, name=None, predicate=None, nr=None):
  463. """Select an HTML form for input.
  464.  
  465. This is a bit like giving a form the "input focus" in a browser.
  466.  
  467. If a form is selected, the Browser object supports the HTMLForm
  468. interface, so you can call methods like .set_value(), .set(), and
  469. .click().
  470.  
  471. Another way to select a form is to assign to the .form attribute. The
  472. form assigned should be one of the objects returned by the .forms()
  473. method.
  474.  
  475. At least one of the name, predicate and nr arguments must be supplied.
  476. If no matching form is found, mechanize.FormNotFoundError is raised.
  477.  
  478. If name is specified, then the form must have the indicated name.
  479.  
  480. If predicate is specified, then the form must match that function. The
  481. predicate function is passed the HTMLForm as its single argument, and
  482. should return a boolean value indicating whether the form matched.
  483.  
  484. nr, if supplied, is the sequence number of the form (where 0 is the
  485. first). Note that control 0 is the first form matching all the other
  486. arguments (if supplied); it is not necessarily the first control in the
  487. form. The "global form" (consisting of all form controls not contained
  488. in any FORM element) is considered not to be part of this sequence and
  489. to have no name, so will not be matched unless both name and nr are
  490. None.
  491.  
  492. """
  493. if not self.viewing_html():
  494. raise BrowserStateError("not viewing HTML")
  495. if (name is None) and (predicate is None) and (nr is None):
  496. raise ValueError(
  497. "at least one argument must be supplied to specify form")
  498.  
  499. global_form = self._factory.global_form
  500. if nr is None and name is None and \
  501. predicate is not None and predicate(global_form):
  502. self.form = global_form
  503. return
  504.  
  505. orig_nr = nr
  506. for form in self.forms():
  507. if name is not None and name != form.name:
  508. continue
  509. if predicate is not None and not predicate(form):
  510. continue
  511. if nr:
  512. nr -= 1
  513. continue
  514. self.form = form
  515. break # success
  516. else:
  517. # failure
  518. description = []
  519. if name is not None: description.append("name '%s'" % name)
  520. if predicate is not None:
  521. description.append("predicate %s" % predicate)
  522. if orig_nr is not None: description.append("nr %d" % orig_nr)
  523. description = ", ".join(description)
  524. raise FormNotFoundError("no form matching "+description)
  525.  
  526. def click(self, *args, **kwds):
  527. """See mechanize.HTMLForm.click for documentation."""
  528. if not self.viewing_html():
  529. raise BrowserStateError("not viewing HTML")
  530. request = self.form.click(*args, **kwds)
  531. return self._add_referer_header(request)
  532.  
  533. def submit(self, *args, **kwds):
  534. """Submit current form.
  535.  
  536. Arguments are as for mechanize.HTMLForm.click().
  537.  
  538. Return value is same as for Browser.open().
  539.  
  540. """
  541. return self.open(self.click(*args, **kwds))
  542.  
  543. def click_link(self, link=None, **kwds):
  544. """Find a link and return a Request object for it.
  545.  
  546. Arguments are as for .find_link(), except that a link may be supplied
  547. as the first argument.
  548.  
  549. """
  550. if not self.viewing_html():
  551. raise BrowserStateError("not viewing HTML")
  552. if not link:
  553. link = self.find_link(**kwds)
  554. else:
  555. if kwds:
  556. raise ValueError(
  557. "either pass a Link, or keyword arguments, not both")
  558. request = self.request_class(link.absolute_url)
  559. return self._add_referer_header(request)
  560.  
  561. def follow_link(self, link=None, **kwds):
  562. """Find a link and .open() it.
  563.  
  564. Arguments are as for .click_link().
  565.  
  566. Return value is same as for Browser.open().
  567.  
  568. """
  569. return self.open(self.click_link(link, **kwds))
  570.  
  571. def find_link(self, **kwds):
  572. """Find a link in current page.
  573.  
  574. Links are returned as mechanize.Link objects.
  575.  
  576. # Return third link that .search()-matches the regexp "python"
  577. # (by ".search()-matches", I mean that the regular expression method
  578. # .search() is used, rather than .match()).
  579. find_link(text_regex=re.compile("python"), nr=2)
  580.  
  581. # Return first http link in the current page that points to somewhere
  582. # on python.org whose link text (after tags have been removed) is
  583. # exactly "monty python".
  584. find_link(text="monty python",
  585. url_regex=re.compile("http.*python.org"))
  586.  
  587. # Return first link with exactly three HTML attributes.
  588. find_link(predicate=lambda link: len(link.attrs) == 3)
  589.  
  590. Links include anchors (<a>), image maps (<area>), and frames (<frame>,
  591. <iframe>).
  592.  
  593. All arguments must be passed by keyword, not position. Zero or more
  594. arguments may be supplied. In order to find a link, all arguments
  595. supplied must match.
  596.  
  597. If a matching link is not found, mechanize.LinkNotFoundError is raised.
  598.  
  599. text: link text between link tags: e.g. <a href="blah">this bit</a> (as
  600. returned by pullparser.get_compressed_text(), ie. without tags but
  601. with opening tags "textified" as per the pullparser docs) must compare
  602. equal to this argument, if supplied
  603. text_regex: link text between tag (as defined above) must match the
  604. regular expression object or regular expression string passed as this
  605. argument, if supplied
  606. name, name_regex: as for text and text_regex, but matched against the
  607. name HTML attribute of the link tag
  608. url, url_regex: as for text and text_regex, but matched against the
  609. URL of the link tag (note this matches against Link.url, which is a
  610. relative or absolute URL according to how it was written in the HTML)
  611. tag: element name of opening tag, e.g. "a"
  612. predicate: a function taking a Link object as its single argument,
  613. returning a boolean result, indicating whether the links
  614. nr: matches the nth link that matches all other criteria (default 0)
  615.  
  616. """
  617. try:
  618. return self._filter_links(self._factory.links(), **kwds).next()
  619. except StopIteration:
  620. raise LinkNotFoundError()
  621.  
  622. def __getattr__(self, name):
  623. # pass through _form.HTMLForm methods and attributes
  624. form = self.__dict__.get("form")
  625. if form is None:
  626. raise AttributeError(
  627. "%s instance has no attribute %s (perhaps you forgot to "
  628. ".select_form()?)" % (self.__class__, name))
  629. return getattr(form, name)
  630.  
  631. def _filter_links(self, links,
  632. text=None, text_regex=None,
  633. name=None, name_regex=None,
  634. url=None, url_regex=None,
  635. tag=None,
  636. predicate=None,
  637. nr=0
  638. ):
  639. if not self.viewing_html():
  640. raise BrowserStateError("not viewing HTML")
  641.  
  642. orig_nr = nr
  643.  
  644. for link in links:
  645. if url is not None and url != link.url:
  646. continue
  647. if url_regex is not None and not re.search(url_regex, link.url):
  648. continue
  649. if (text is not None and
  650. (link.text is None or text != link.text)):
  651. continue
  652. if (text_regex is not None and
  653. (link.text is None or not re.search(text_regex, link.text))):
  654. continue
  655. if name is not None and name != dict(link.attrs).get("name"):
  656. continue
  657. if name_regex is not None:
  658. link_name = dict(link.attrs).get("name")
  659. if link_name is None or not re.search(name_regex, link_name):
  660. continue
  661. if tag is not None and tag != link.tag:
  662. continue
  663. if predicate is not None and not predicate(link):
  664. continue
  665. if nr:
  666. nr -= 1
  667. continue
  668. yield link
  669. nr = orig_nr
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement