0

I am trying to scrape this page recursively using BeautifulSoup.

The problem however is that the pdf links actually open a new page on which the pdf's are embedded. In this embedded page we can subsequently find the true pdf links from the embedded tag.

I added therefore a line to check if the content is of the application/pdf. However using the redirect url, I am unable to extract the pdf links from this new page with the embedded pdf.

I tried the following but this did not work (a valid pdf link is never found)

# run the following in a .py file:
# spider = fdb.OurSpider()
# spider.scrape_page(url=url)

import os
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
from requests import get

import time

MAX_DEPTH = 10

class OurSpider:

    def __init__(self):
        """Init our Custom Spider"""

    def scrape_page(self, url):
        """Scrape page"""

        try:
            self.download_pdfs(url=url)

        except requests.exceptions.MissingSchema:
            print(f'skipped MissingSchema [{url}]')

            try:
                links = self.get_links(url=url)
                print(links)
            except:
                print('')

    def download_pdfs(self, url, depth=1):
        # If there is no such folder, the script will create one automatically
        print('')
        print(f'--- [{depth}] {url}')
        if depth > MAX_DEPTH:
            return 'max depth reached'

        soup = self.get_soup(url=url)
        links = soup.select("a[href$='.pdf']")

        for link in links:
            try:
                full_url = urljoin(url, link['href'])
                content = get(full_url)
                if content.status_code == 200 and content.headers['content-type'] == 'application/pdf':
                    self.download_pdf(full_url=full_url)

                elif full_url != url:
                    self.download_pdfs(url=full_url, depth=depth+1)

                else:
                    print('skipping url')

            except requests.exceptions.InvalidSchema:
                print(f'skipped InvalidSchema [{link}]')

        print('--- downloading pdfs done')

    def download_pdf(self, full_url):
        """Download single url"""

        filename = "".join(['tmp/', str(return round(time.time() * 1000)), '.pdf'])
        if not self.file_exists(filename=filename):

            print(f'{filename}: {full_url}')
            with open(filename, 'wb') as f:
                f.write(requests.get(full_url).content)

    def get_links(self, url):
        """Get the links given the url"""
        soup = self.get_soup(url=url)
        return soup.findAll('a', href=True)

    @staticmethod
    def file_exists(filename):
        """File exists locally"""
        return os.path.exists(filename)

    @staticmethod
    def get_soup(url):
        """Init the url"""
        response = requests.get(url)
        soup = BeautifulSoup(response.text, "html.parser")
        return soup
JohnAndrews
  • 5,088
  • 9
  • 57
  • 121

1 Answers1

2
import requests
from bs4 import BeautifulSoup
from concurrent.futures import ThreadPoolExecutor, as_completed
import re
from urllib.parse import unquote

site = "https://www.masked.com/us/individual/resources/regulatory-documents/mutual-funds"


def main(url):
    r = requests.get(url)
    soup = BeautifulSoup(r.content, 'html.parser')
    target = [f"{url[:25]}{item.get('href')}"
              for item in soup.findAll("a", title="Annual Report")]
    return target


def parse(url):
    with requests.Session() as req:
        r = req.get(url)
        match = [unquote(f"{r.url[:25]}{match.group(1)}") for match in re.finditer(
            r"Override=(.+?)\"", r.text)]
        return match


with ThreadPoolExecutor(max_workers=50) as executor:
    futures = [executor.submit(parse, url) for url in main(site)]

links = []
for future in futures:
    links.extend(future.result())

print(f"Collected {len(links)}")


def download(url):
    with requests.Session() as req:
        r = req.get(url)
        if r.status_code == 200 and r.headers['Content-Type'] == "application/pdf;charset=UTF-8":
            name = r.url.rfind("/") + 1
            name = r.url[name:]
            return f"Saving {name}"
            with open(f"{name}", 'wb') as f:
                f.write(r.content)
        else:
            pass


with ThreadPoolExecutor(max_workers=50) as executor:
    futures = [executor.submit(download, url) for url in links]

for future in as_completed(futures):
    print(future.result())

  • Looks good, do you mind just hiding the url in the answer? For regul purposes. I will try your code tomorrow. – JohnAndrews Apr 02 '20 at 19:13
  • Wow, that actually works :) What did you exactly do to make this work and what do you think was the problem? – JohnAndrews Apr 03 '20 at 07:46
  • 1
    @JohnAndrews Just used threads to collect all links, then parsed that parameter `Override` with regex where it's hold the real location behind. – αԋɱҽԃ αмєяιcαη Apr 03 '20 at 09:27
  • But would it work on other sites? Like the override is not just for the structure of this site? – JohnAndrews Apr 03 '20 at 09:44
  • 1
    @JohnAndrews your question is out of knowledge of site structure at all. Basically `Override` parameter is a param where it's handle a redirection to another location within the same host. Indeed each host have it's own case. for that particular one, we have catch the final destination source. for other sites, you would allow redirection in case if there's redirection then catch final destination. – αԋɱҽԃ αмєяιcαη Apr 03 '20 at 09:46
  • Any libraries that can handle any type instead of having to write all cases? – JohnAndrews Apr 03 '20 at 13:48
  • @JohnAndrews that's can't be done at all! what if the host redirect you to a captcha ? what if the host redirect you and asking for input before download ? what if it's requiring a click ? only you can handle this via `Machine Learning` model – αԋɱҽԃ αмєяιcαη Apr 03 '20 at 13:53
  • Any reading material on this? Like what should I look for on the web to handle some the most common cases. How is this called? – JohnAndrews Apr 03 '20 at 13:58
  • @αԋɱҽԃαмєяιcαη obsessed with your background. – El_1988 Nov 19 '20 at 00:32