juriscraper

repository·main·Indexed 20 days ago

https://github.com/freelawproject/juriscraper

A Python library and API for scraping judicial opinions, oral arguments, and PACER data from American court websites. It provides a framework for metadata extraction through base classes like AbstractSite, OpinionSite, and ClusterSite, and includes tools for handling historical content via download_backwards, bypassing TLS fingerprinting, and implementing custom parsers.

Tokens
12.5K
Snippets
39
Records
53
Agent score
70%

What's inside juriscraper

  1. Understand the `goDLS` function for PACER document downloads

    main

    The goDLS function is used in PACER to generate a form, append it to the document, and submit it to trigger a document download (typically a PDF). When automating PACER, you may need to replicate this POST request behavior.

    District Court goDLS Parameters

    When calling goDLS in District courts, the following parameters are used in the POST body:

    • hyperlink: The HTML 'action' attribute (the URL where the form is posted).
    • caseid: The internal PACER ID for the case (de_caseid).
    • de_seq_num: The internal PACER document number within the case (de_seqno). Note that this may differ from the visible document number.
    • got_receipt: If set to '1', it bypasses the receipt page and downloads the PDF immediately. This is typically set after a user has viewed the receipt.
    • pdf_header: Controls the blue header on generated PDFs. 1 includes the header, 2 excludes it.
    • pdf_toggle_possible: (Unknown purpose).
    • magic_num: (Used when an NEF hyperlink is clicked on multi-document filings).
    • hdr: (Used when an NEF hyperlink is clicked on ROA/Appendix).

    Bankruptcy CMECF goDLS Parameters

    In Bankruptcy CMECF, the signature changes to include bankruptcy-specific parameters for claim documents, replacing the hdr parameter:

    • claim_id: Bankruptcy-specific claim ID.
    • claim_num: Bankruptcy-specific claim number.
    • claim_doc_seq: Bankruptcy-specific claim document sequence.
    // District Court signature
    function goDLS(hyperlink, de_caseid, de_seqno, got_receipt, pdf_header, pdf_toggle_possible, magic_num, hdr) {
        // ... implementation ...
    }
    
    // Bankruptcy CMECF signature
    function goDLS(hyperlink, de_caseid, de_seqno, got_receipt, pdf_header, pdf_toggle_possible, magic_num, claim_id, claim_num, claim_doc_seq) {
        // ... implementation ...
    }
  2. How Juriscraper works

    main

    Juriscraper is a library designed to gather judicial opinions, oral arguments, and PACER data. It is not a standalone application but a component of a two-part system:

    1. Juriscraper (The Library): Provides the scraping logic, XPath-based extraction (via lxml), and metadata retrieval.
    2. Your Code (The Caller): Your application is responsible for calling the Juriscraper classes, managing the execution flow, and downloading/saving the results.

    Juriscraper is designed to be extensible (for new geographies or media types like video/audio) and returns all available metadata from court websites, allowing the caller to decide which fields to persist.

  3. Understand PACER doc1 URLs

    main

    Every document in PACER can be accessed via a doc1 URL. These URLs follow a specific structure that allows for direct document downloading by manipulating the fourth digit.

    URL Structure: https://[jurisdiction].uscourts.gov/doc1/[id]

    • Jurisdiction: The domain prefix (e.g., ecf.nysd.uscourts.gov).
    • ID Breakdown: The numeric ID at the end contains metadata:
      • First three digits: Correspond to the court (e.g., 127 is nysd, 128 is nywb).
      • Fourth digit: Indicates attachment status. A 1 means the item has no attachments and can be downloaded directly. A 0 means the item has attachments and will lead to an attachments list.

    Tip: To bypass the attachments list screen and download the item directly, you can manually change a 0 to a 1 in the URL.

    https://ecf.nysd.uscourts.gov/doc1/12716951218
  4. Configure WebDriver for scraping

    main

    Some scrapers require an automated WebDriver (like Geckodriver for Firefox). You can use a local installation or connect to a remote WebDriver (e.g., a Selenium Docker container) using environment variables.

    Local Installation

    Download the appropriate Geckodriver package for your OS from the Geckodriver releases, extract it, and move it to your path:

    sudo mv geckodriver /usr/local/bin

    Remote WebDriver Configuration

    Use the following environment variables to control WebDriver behavior:

    • WEBDRIVER_CONN: The connection string for the remote driver. Defaults to local. To use a remote driver, set this to the URL (e.g., http://YOUR_DOCKER_IP:4444/wd/hub).
    • SELENIUM_VISIBLE: Set this to any value to disable headless mode (if the driver supports it).
    docker run \
        -p 4444:4444 \
        -p 5900:5900 \
        -v /dev/shm:/dev/shm \
        selenium/standalone-firefox-debug
  5. Install Juriscraper and its dependencies

    main

    Juriscraper requires Python 3.9+ and several system-level libraries for XML and YAML processing. Follow the steps for your specific operating system before installing the Python package.

    ### 1. Install System Dependencies
    
    # Ubuntu/Debian
    sudo apt-get install libxml2-dev libxslt-dev libyaml-dev
    
    # Arch Linux
    sudo pacman -S libxml2 libxslt libyaml
    
    # macOS (Homebrew)
    brew install libyaml
    
    ### 2. Install the Python package
    pip install juriscraper
  6. Submit PACER forms using Python requests

    main

    PACER forms often use multipart/form-data encoding even when no actual files are being uploaded. To submit credentials (like username and password) via these forms using the Python requests library, you must pass the data as a dictionary to the files parameter. The keys in the dictionary should match the form field names (e.g., login and key), and the values should be tuples containing an empty string for the filename and the actual data string.

    r = s.post(
        url,
        headers={'User-Agent': 'Juriscraper'},
        verify=certifi.where(),
        timeout=30,
        files={
            'login': ('', username),
            'key': ('', password)
        },
    )
  7. Retrieve historical content using download_backwards

    main

    If you need to retrieve historical content, do not use the methods located in the united_states_backscrapers directory. Instead, use the download_backwards method. This method is designed to simplify the process of fetching historical data, even in complex scenarios.

    # Use download_backwards instead of backscrapers for historical content
    download_backwards(...)
  8. What is the difference between Parser and AbstractParser?

    main

    The repository provides two distinct base class hierarchies:

    1. Parser[_ParserInput, _ParserOutput]: The modern, generic interface. It provides a robust lifecycle including automatic caching of results and a built-in validation step. It is designed for type-safe, reusable parsing logic.

    2. AbstractParser[_ParserOutput]: A simpler, more minimal abstract base class. It does not provide the automatic caching or validation lifecycle found in Parser. It requires the user to implement _parse_text and the data property manually.

  9. Extend AbstractSite to create a new scraper

    main

    The AbstractSite class is the base class for all Juriscraper implementations. To create a new scraper, you must subclass AbstractSite and implement the necessary data-gathering methods.

    Key lifecycle steps in a scraper include:

    1. Initialization: Define court_id, url, parameters, and the list of attributes to scrape in _all_attrs (e.g., self._all_attrs = ['case_names', 'case_dates', ...]).
    2. Parsing: The parse() method (which you typically call) orchestrates the download and then calls _get_<attr>() methods for every attribute listed in _all_attrs to populate the data.
    3. Data Cleaning: The class automatically calls _clean_attributes() to sanitize scraped data.
    4. Post-Processing: You can override _post_parse() to perform custom logic after all attributes are populated.
    5. Sanity Checking: The class runs _check_sanity() to ensure all attribute lists have matching lengths and required fields are present.
    from juriscraper.AbstractSite import AbstractSite
    
    class MyNewCourtScraper(AbstractSite):
        def __init__(self, cnt=None, **kwargs):
            super().__init__(cnt=cnt, **kwargs)
            self.court_id = "my_court"
            self.url = "https://example.com/cases"
            self._all_attrs = ["case_names", "case_dates", "case_numbers"]
    
        async def _get_case_names(self):
            # Implementation to extract case names from self.html
            return ["Case A", "Case B"]
    
        async def _get_case_dates(self):
            # Implementation to extract case dates
            return [datetime(2023, 1, 1), datetime(2023, 1, 2)]
    
        async def _get_case_numbers(self):
            return ["1", "2"]
    
    # Usage:
    # scraper = MyNewCourtScraper()
    # await scraper.parse()
    # print(scraper.to_json())
  10. Implement a linear opinion site using OpinionSiteLinear

    main

    Use OpinionSiteLinear when a website's opinions are presented in a linear list format rather than requiring complex, separate HTML path parsing for every single attribute.

    To use this class, you must:

    1. Extend OpinionSiteLinear.
    2. Implement the _process_html() method. This method is responsible for parsing the HTML and populating self.cases with a list of dictionaries.
    3. Ensure the dictionaries in self.cases use valid shorthand keys (see Valid Keys for OpinionSiteLinear).
    4. (Optional) If all cases on the page share the same status, define self.status in your __init__ method. Otherwise, include a status key in each case dictionary.
    from juriscraper.OpinionSiteLinear import OpinionSiteLinear
    
    class MyLinearSite(OpinionSiteLinear):
        def _process_html(self):
            # Logic to parse HTML and populate self.cases
            self.cases = [
                {
                    "name": "Case Name",
                    "url": "http://example.com/case1",
                    "date": "2023-01-01",
                    "status": "Published",
                    "docket": "12345",
                    # ... other keys
                }
            ]
  11. Use ClusterSite for aggregating multiple opinion sources

    main

    The ClusterSite class is a specialized version of OpinionSiteLinear designed for sites that aggregate data from multiple sources into a single case. Unlike standard sites that return a list of attributes, ClusterSite uses a dictionary representation for each case.

    In a ClusterSite, a single 'case' returned by the scraper represents an opinion cluster. Instead of a single set of opinion attributes (like authors or urls), the cluster contains a sub_opinions list. Each item in sub_opinions contains the specific details for an individual opinion within that cluster.

    Key characteristics:

    • Data Format: Returns a list of dictionaries (accessible via __iter__, __getitem__, or len()).
    • Clustering Logic: Opinions are grouped based on matching name, docket, and date.
    • Date Flexibility: You can control how strictly dates must match by setting cluster_by_date_max_days.
    class MyClusterSite(ClusterSite):
        # Implementation would involve overriding parse() or providing data 
        # that results in a list of dictionaries where opinions are nested 
        # under the 'sub_opinions' key.
  12. Implement a linear scraper using OralArgumentSiteLinear

    main

    Use OralArgumentSiteLinear when a website's oral argument data is presented in a linear list format rather than requiring complex HTML path parsing for individual elements. To use this class, extend it and implement the _process_html() method.

    Inside _process_html(), you must populate self.cases with a list of dictionaries. Each dictionary in self.cases should contain the following keys to support the standard getters:

    • name: The name of the case.
    • url: The download URL for the oral argument.
    • date: The date string (which will be processed by convert_date_string).
    • docket: The docket number.

    Optional keys that can be included in the dictionaries to support optional getters include:

    • judge: To use _get_judges().
    • attorney: To use _get_attorneys().
    from juriscraper.OralArgumentSiteLinear import OralArgumentSiteLinear
    
    class MyLinearSite(OralArgumentSiteLinear):
        def _process_html(self):
            # Logic to parse HTML and populate self.cases
            self.cases = [
                {
                    "name": "Case Name",
                    "url": "https://example.com/audio.mp3",
                    "date": "January 1, 2023",
                    "docket": "123-ABC",
                    "judge": "Judge Smith",
                    "attorney": "Attorney Doe"
                }
            ]
    
    # Usage
    scraper = MyLinearSite(url="https://example.com")
    scraper._process_html()
    print(scraper._get_case_names())