【问题标题】:Python requests: URL base in SessionPython 请求:Session 中的 URL 基础
【发布时间】:2022-04-08 00:40:09
【问题描述】:

使用 Session 时,似乎每次都需要提供完整的 URL,例如

session = requests.Session()
session.get('http://myserver/getstuff')
session.get('http://myserver/getstuff2')

这有点乏味。有没有办法做类似的事情:

session = requests.Session(url_base='http://myserver')
session.get('/getstuff')
session.get('/getstuff2')

【问题讨论】:

    标签: python python-requests


    【解决方案1】:

    这个功能已经在论坛上被问过几次123。记录在 here 中的首选方法是子类化,如下所示:

    from requests import Session
    from urlparse import urljoin
    
    class LiveServerSession(Session):
        def __init__(self, prefix_url=None, *args, **kwargs):
            super(LiveServerSession, self).__init__(*args, **kwargs)
            self.prefix_url = prefix_url
    
        def request(self, method, url, *args, **kwargs):
            url = urljoin(self.prefix_url, url)
            return super(LiveServerSession, self).request(method, url, *args, **kwargs)
    

    您可以按如下方式简单地使用它:

    baseUrl = 'http://api.twitter.com'
    with LiveServerSession(baseUrl) as s:
        resp = s.get('/1/statuses/home_timeline.json')
    

    【讨论】:

    • 我喜欢这个解决方案,但有点遗憾的是,必须在丢失所有以下 14 个参数的类型之间做出选择,或者在复制所有这些参数及其类型的情况下实现该方法。
    【解决方案2】:

    requests_toolbelt.sessions.BaseUrlSession https://github.com/requests/toolbelt/blob/f5c86c51e0a01fbc8b3b4e1c286fd5c7cb3aacfa/requests_toolbelt/sessions.py#L6

    注意:这使用标准库中的 urljoin。当心 urljoin 的行为。

    In [14]: from urlparse import urljoin
    
    In [15]: urljoin('https://localhost/api', '/resource')
    Out[15]: 'https://localhost/resource'
    
    In [16]: urljoin('https://localhost/api', 'resource')
    Out[16]: 'https://localhost/resource'
    
    In [17]: urljoin('https://localhost/api/', '/resource')
    Out[17]: 'https://localhost/resource'
    
    In [18]: urljoin('https://localhost/api/', 'resource')
    Out[18]: 'https://localhost/api/resource'
    

    import requests 
    from functools import partial
    
    def PrefixUrlSession(prefix=None):                                                                                                                                                                                                                                                                                                                 
         if prefix is None:                                                                                                                                                                                                                                                                                                                             
             prefix = ""                                                                                                                                                                                                                                                                                                                                
         else:                                                                                                                                                                                                                                                                                                                                          
             prefix = prefix.rstrip('/') + '/'                                                                                                                                                                                                                                                                                                          
    
         def new_request(prefix, f, method, url, *args, **kwargs):                                                                                                                                                                                                                                                                                      
             return f(method, prefix + url, *args, **kwargs)                                                                                                                                                                                                                                                                                            
    
         s = requests.Session()                                                                                                                                                                                                                                                                                                                         
         s.request = partial(new_request, prefix, s.request)                                                                                                                                                                                                                                                                                            
         return s             
    

    【讨论】:

    • 我确信删除 URI 中的 api 部分是有道理的,但是天哪,如果不是在清晨喝一杯新鲜的咖啡,我会坚持几个小时,想知道为什么我的请求电话不起作用。
    • 在 python3 中,urlparse 模块已被重命名。你应该改用from urllib.parse import urljoin
    • url = urljoin(self.prefix_url.rstrip("/") + "/", url.lstrip("/")) 避免urljoin 的烦人行为...
    • @vincent 它反映了浏览器如何解析 URL。 "/resource" 是一个绝对 URL,因此它会忽略当前路径。并且“/api”看起来像一个文件(与“/api/”相反,它被假定为“/api/index.html”或其他东西),因此将“资源”附加到它是没有意义的.
    【解决方案3】:

    您可以只继承 request.Session 并重载其 __init__request 方法,如下所示:

    # my_requests.py
    import requests
    
    
    class SessionWithUrlBase(requests.Session):
        # In Python 3 you could place `url_base` after `*args`, but not in Python 2.
        def __init__(self, url_base=None, *args, **kwargs):
            super(SessionWithUrlBase, self).__init__(*args, **kwargs)
            self.url_base = url_base
    
        def request(self, method, url, **kwargs):
            # Next line of code is here for example purposes only.
            # You really shouldn't just use string concatenation here,
            # take a look at urllib.parse.urljoin instead.
            modified_url = self.url_base + url
    
            return super(SessionWithUrlBase, self).request(method, modified_url, **kwargs)
    

    然后你可以在你的代码中使用你的子类而不是requests.Session

    from my_requests import SessionWithUrlBase
    
    
    session = SessionWithUrlBase(url_base='https://*.com/')
    session.get('documentation')  # https://*.com/documentation
    

    您还可以对requests.Session 进行猴子补丁以避免修改现有代码库(此实现应 100% 兼容),但请务必在任何代码调用 requests.Session() 之前进行实际修补:

    # monkey_patch.py
    import requests
    
    
    class SessionWithUrlBase(requests.Session):
        ...
    
    requests.Session = SessionWithUrlBase
    

    然后:

    # main.py
    import requests
    import monkey_patch
    
    
    session = requests.Session()
    repr(session)  # <monkey_patch.SessionWithUrlBase object at ...>
    

    【讨论】:

      【解决方案4】:

      我没有看到执行此操作的内置方法,但您可以使用包装函数来添加您想要的功能:

      from functools import wraps
      import inspect
      import requests
      from requests.compat import urljoin
      
      def _base_url(func, base):
          '''Decorator for adding a base URL to func's url parameter'''
      
          @wraps(func)
          def wrapper(*args, **kwargs):
              argname = 'url'
              argspec = inspect.getargspec(func)
      
              if argname in kwargs:
                  kwargs[argname] = urljoin(base, kwargs[argname])
              else:
                  # Find and replace url parameter in positional args. The argspec
                  # includes self while args doesn't, so indexes have to be shifted
                  # over one
                  for i, name in enumerate(argspec[0]):
                      if name == argname:
                          args = list(args)
                          args[i-1] = urljoin(base, args[i-1])
                          break
      
              return func(*args, **kwargs)
          return wrapper
      
      def inject_base_url(func):
          '''Decorator for adding a base URL to all methods that take a url param'''
      
          @wraps(func)
          def wrapper(*args, **kwargs):
              argname = 'base_url'
      
              if argname in kwargs:
                  obj = args[0]
      
                  # Add base_url decorator to all methods that have a url parameter
                  for name, method in inspect.getmembers(obj, inspect.ismethod):
                      argspec = inspect.getargspec(method.__func__)
      
                      if 'url' in argspec[0]:
                          setattr(obj, name, _base_url(method, kwargs[argname]))
      
                  del kwargs[argname]
      
              return func(*args, **kwargs)
          return wrapper
      
      # Wrap requests.Session.__init__ so it takes a base_url parameter
      setattr(
          requests.Session,
          '__init__',
          inject_base_url(getattr(requests.Session, '__init__'))
      )
      

      现在您可以在构造新的 requests.Session 对象时指定基本 URL:

      s = requests.Session(base_url='http://*.com')
      s.get('questions')      # http://*.com/questions
      s.post('documentation') # http://*.com/documentation
      
      # With no base_url, you get the default behavior
      s = requests.Session()
      s.get('http://google.com')
      

      【讨论】:

      • 我喜欢这个答案,但它仅在基本 url 没有子级别时才有效,因为urljoin 使用作为获取和发布方法的 url 提供的内容覆盖它们。我需要它,所以我用简单的字符串连接替换了urljoin 调用
      【解决方案5】:

      保持简单并使用内置方法加入(没有'/'后缀麻烦):

      import urllib.parse
      session = requests.Session()
      session.my_base_url_join = lambda path: urllib.parse.urljoin(str_BASE_URL, path)
      # use: session.get(session.my_base_url_join(path='/message'))
      
      

      【讨论】:

        【解决方案6】:

        根据@qrtLs 的回答,这是一个 3-4 行版本,可以满足您的需求(假设您不需要创建多个会话,只需要定义 get 方法)。

        import requests
        import urllib.parse
        
        session = requests.Session()
        session.base_url = "https://google.com/"
        session.get = lambda *args, **kwargs: requests.Session.get(session, urllib.parse.urljoin(s.base_url, args[0]), *args[1:], **kwargs)
        
        r = session.get("/search?q=asdf", verify=False)
        

        【讨论】:

          最近更新 更多