5
\$\begingroup\$

I wrote a short program which should allow a user to specify a starting page in Discogs Wiki Style Guide, scrape the other styles listed on the page, and then output a graph (represented here as a dictionary of sets) of the relationship between subgenres.

I'm looking for guidance/critique on: (1) How to clean up the request_page function, I think there is a more elegant way both getting href attrs and filtering to only those with "/style/". (2) The general structure of the program. Self-taught and relative beginner so it's highly appreciated if anyone could point out general irregularities.

import re
import requests 
from bs4 import BeautifulSoup 

def get_related_styles(start):

    def request_page(start):

        response = requests.get('{0}{1}'.format(base_style_url, start))
        soup = BeautifulSoup(response.content,'lxml')

        ## these lines feel inelegant. considered solutions with
        ## soup.findAll('a', attrs = {'href': pattern.match})

        urls = [anchor.get('href') for anchor in soup.findAll('a')]
        pattern = re.compile('/style/[a-zA-Z0-9\-]*[^/]') # can use lookback regex w/ escape chars?
        style_urls = {pattern.match(url).group().replace('/style/','') for url in urls if pattern.match(url)}

        return style_urls

    def connect_styles(start , style_2):

        ## Nodes should not connect to self
        ## Note that styles are directed - e.g. (A ==> B) =/=> (B ==> A)

        if start != style_2:
            if start not in all_styles.keys():
                all_styles[start] = {style_2}

            else:
                all_styles[start].add(style_2)

        if style_2 not in do_not_visit:
            do_not_visit.add(style_2)
            get_related_styles(style_2)

    style_urls = request_page(start)

    for new_style in style_urls:
        connect_styles(start,new_style)

Example Use:

start = 'Avant-garde-Jazz'
base_style_url = 'https://reference.discogslabs.com/style/'

all_styles = {}
do_not_visit = {start}

get_related_styles(start)

print(all_styles)
{'Free-Jazz': {'Free-Improvisation', 'Free-Funk'}, 'Free-Improvisation': {'Free-Jazz', 'Avant-garde-Jazz'}, 'Avant-garde-Jazz': {'Free-Jazz'}, 'Free-Funk': {'Free-Jazz'}}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

There is a simpler way to filter out the "style" links - using a CSS selector with a partial match on the href attribute:

style_urls = {anchor['href'].replace('/style/', '') 
              for anchor in soup.select('a[href^="/style/"]')]

where ^= means "starts with".

Here we, of course, lose the check we had on the style name part of the href. If this check is really needed, we can also use a regular expression to match the desired style links directly:

pattern = re.compile('/style/([a-zA-Z0-9\-]*)[^/]')
style_urls = {pattern.search(anchor['href']).group(1)
              for anchor in soup('a', href=pattern)

soup() here is a short way of doing soup.find_all().

\$\endgroup\$

You must log in to answer this question.