diff --git a/README.md b/README.md
index 86b0e81..a8dbe3d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,5 @@
 # Google Spreadsheets Python API
 
-<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=TK7NPLQ6XEPKS&lc=US&item_name=gspread&currency_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted" target="_blank"><img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif" /></a>
-
-
 Manage your spreadsheets with _gspread_ in Python.
 
 Features:
diff --git a/gspread/client.py b/gspread/client.py
index b43c099..b0dc686 100644
--- a/gspread/client.py
+++ b/gspread/client.py
@@ -8,7 +8,6 @@ This module contains Client class responsible for communicating with
 Google Data API.
 
 """
-import re
 import json
 
 try:
@@ -22,17 +21,13 @@ from .ns import _ns
 from .httpsession import HTTPSession, HTTPError
 from .models import Spreadsheet
 from .urls import construct_url
-from .utils import finditem
-from .exceptions import (SpreadsheetNotFound, NoValidUrlKeyFound,
-                         UpdateCellError, RequestError)
+from .utils import finditem, extract_id_from_url
+from .exceptions import (SpreadsheetNotFound, UpdateCellError, RequestError)
 
 
 AUTH_SERVER = 'https://www.google.com'
 SPREADSHEETS_SERVER = 'spreadsheets.google.com'
 
-_url_key_re_v1 = re.compile(r'key=([^&#]+)')
-_url_key_re_v2 = re.compile(r'spreadsheets/d/([^&#]+)/edit')
-
 
 class Client(object):
 
@@ -111,14 +106,9 @@ class Client(object):
         for elem in feed.findall(_ns('entry')):
             alter_link = finditem(lambda x: x.get('rel') == 'alternate',
                                   elem.findall(_ns('link')))
-            m = _url_key_re_v1.search(alter_link.get('href'))
-            if m and m.group(1) == key:
-                return Spreadsheet(self, elem)
-
-            m = _url_key_re_v2.search(alter_link.get('href'))
-            if m and m.group(1) == key:
+            spreadsheet_id = extract_id_from_url(alter_link.get('href'))
+            if spreadsheet_id == key:
                 return Spreadsheet(self, elem)
-
         else:
             raise SpreadsheetNotFound
 
@@ -136,17 +126,7 @@ class Client(object):
         >>> c.open_by_url('https://docs.google.com/spreadsheet/ccc?key=0Bm...FE&hl')
 
         """
-        m1 = _url_key_re_v1.search(url)
-        if m1:
-            return self.open_by_key(m1.group(1))
-
-        else:
-            m2 = _url_key_re_v2.search(url)
-            if m2:
-                return self.open_by_key(m2.group(1))
-
-            else:
-                raise NoValidUrlKeyFound
+        return self.open_by_key(extract_id_from_url(url))
 
     def openall(self, title=None):
         """Opens all available spreadsheets.
diff --git a/gspread/utils.py b/gspread/utils.py
index 0ec8f69..b19217f 100644
--- a/gspread/utils.py
+++ b/gspread/utils.py
@@ -11,11 +11,14 @@ This module contains utility functions.
 import re
 from xml.etree import ElementTree
 
-from .exceptions import IncorrectCellLabel
+from .exceptions import IncorrectCellLabel, NoValidUrlKeyFound
 
 MAGIC_NUMBER = 64
 CELL_ADDR_RE = re.compile(r'([A-Za-z]+)([1-9]\d*)')
 
+URL_KEY_V1_RE = re.compile(r'key=([^&#]+)')
+URL_KEY_V2_RE = re.compile(r'/spreadsheets/d/([a-zA-Z0-9-_]+)')
+
 
 def finditem(func, seq):
     """Finds and returns first item in iterable for which func(item) is True.
@@ -164,7 +167,17 @@ def a1_to_rowcol(label):
     else:
         raise IncorrectCellLabel(label)
 
-    return (row, col)
+
+def extract_id_from_url(url):
+    m2 = URL_KEY_V2_RE.search(url)
+    if m2:
+        return m2.group(1)
+
+    m1 = URL_KEY_V1_RE.search(url)
+    if m1:
+        return m1.group(1)
+
+    raise NoValidUrlKeyFound
 
 
 if __name__ == '__main__':

Incorrect parsing of URLs in "gc.open_by_url" (new Drive URL format?)
I tried using "gc.open_by_url" for the first time, and got a "gspread.exceptions.NoValidUrlKeyFound." My URL looked like this (key modified): 

```
baselinedata = gc.open_by_url("https://docs.google.com/spreadsheets/d/1qTqNDk5xYnapVa2Uuc2jIFL9qUuZ7iDMxB388w3L99c")
```

The key itself is correct. My suspicion was that possibly the Google Drive URLs may have themselves changed, since the example you give in the guide is different: "https://docs.google.com/spreadsheet/ccc?key=0Bm...FE&hl'" (so, it has no ".../d/..." and mine doesn't have the "/ccc?key=" part). 

diff --git a/tests/test.py b/tests/test.py
index 2f4f8aa..af73728 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -13,6 +13,7 @@ except ImportError:
 from oauth2client.service_account import ServiceAccountCredentials
 
 import gspread
+from gspread import utils
 
 try:
     unicode
@@ -47,6 +48,39 @@ def gen_value(prefix=None):
         return unicode(uuid.uuid4())
 
 
+class UtilsTest(unittest.TestCase):
+    def test_extract_id_from_url(self):
+        url_id_list = [
+            # New-style url
+            ('https://docs.google.com/spreadsheets/d/'
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps/edit#gid=0',
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps'),
+
+            ('https://docs.google.com/spreadsheets/d/'
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps/edit',
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps'),
+
+            ('https://docs.google.com/spreadsheets/d/'
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps',
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps'),
+
+            # Old-style url
+            ('https://docs.google.com/spreadsheet/'
+             'ccc?key=1qpyC0X3A0MwQoFDE8p-Bll4hps&usp=drive_web#gid=0',
+             '1qpyC0X3A0MwQoFDE8p-Bll4hps')
+        ]
+
+        for url, id in url_id_list:
+            self.assertEqual(id, utils.extract_id_from_url(url))
+
+    def test_no_extract_id_from_url(self):
+        self.assertRaises(
+            gspread.NoValidUrlKeyFound,
+            utils.extract_id_from_url,
+            'http://example.org'
+        )
+
+
 class GspreadTest(unittest.TestCase):
 
     @classmethod

pip install -e .[dev]
parse_log_pytest
requirements.txt
nose requests[security] oauth2client pytest
3.9
test-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
certifi==2025.1.31
charset-normalizer==3.4.1
exceptiongroup==1.2.2
-e git+https://github.com/burnash/gspread.git@51ce7ab1ee87f738ccae40385149cc859e1379f6#egg=gspread
httplib2==0.22.0
idna==3.10
iniconfig==2.1.0
nose==1.3.7
oauth2client==4.1.3
packaging==24.2
pluggy==1.5.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pyparsing==3.2.3
pytest==8.3.5
requests==2.32.3
rsa==4.9
six==1.17.0
tomli==2.2.1
urllib3==2.3.0

name: gspread
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - exceptiongroup==1.2.2
      - httplib2==0.22.0
      - idna==3.10
      - iniconfig==2.1.0
      - nose==1.3.7
      - oauth2client==4.1.3
      - packaging==24.2
      - pluggy==1.5.0
      - pyasn1==0.6.1
      - pyasn1-modules==0.4.2
      - pyparsing==3.2.3
      - pytest==8.3.5
      - requests==2.32.3
      - rsa==4.9
      - six==1.17.0
      - tomli==2.2.1
      - urllib3==2.3.0
prefix: /opt/conda/envs/gspread

tests/test.py::UtilsTest::test_extract_id_from_url tests/test.py::UtilsTest::test_no_extract_id_from_url
README.md gspread/client.py gspread/utils.py
README.md gspread/client.py gspread/utils.py