<jupyter_start><jupyter_text>Modelo de datos La dificultad de recoger datos de Crunchbase reside en que cada página tiene una serie de campos que no son necesariamente los mismos. Por lo tanto, hemos considerado que en este caso **el modelo de datos debe ser dinámico**, puesto que no conocemos de antemano qué variables vamos a almacenar. Hemos definido la siguiente función que mapea los campos informados de un perfil en concreto a un diccionario {clave: valor}, que añadiremos más tarde al conjunto de datos. También hemos incluido un *log* que nos permite ver en tiempo real la información que se está recogiendo.<jupyter_code>def parse2row(soup, log=False):
    row = {}
    print("---") if log else None
    try:
        name = soup.select('h1[class="profile-name"]')[0].text.strip()
        row["Name"] = name
        print("Name: " + name) if log else None
        
        profile = soup.select('div[class="profile-type"] > span')[0].text
        row["Profile"] = profile
        print("Profile: " + profile) if log else None
    except IndexError:
        pass
    
    fields = soup.select('page-centered-layout .main-content profile-section .section-content fields-card > ul li')
    for li in fields:
        value = ''
        try:
            label = list(li.select('label-with-info')[0].stripped_strings)[0]
            values = list(li.select('field-formatter')[0].stripped_strings)

            try:
                while True:
                    values.remove(',')
            except ValueError:
                pass        
            value = ', '.join(values)

            row[label] = value
            print(label + ': ' + value) if log else None
        except IndexError:
            pass

    return row<jupyter_output><empty_output><jupyter_text>Resolución de obstáculos en web scraping  Modificación del *user agent* y otras cabeceras HTTP De forma predeterminada, las bibliotecas utilizadas para realizar peticiones HTTP de forma automática establecen su propio *user agent* basándose en el nombre de la librería, facilitándole la tarea a Crunchbase que, por defecto, bloquea el acceso de todos los robots a su página web. Modificamos el *user agent* y otras cabeceras HTTP para ocultar el hecho de que las peticiones realizadas provienen de un script<jupyter_code>headers = {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,\
    */*;q=0.8",
    "Accept-Encoding": "gzip, deflate, sdch, br",
    "Accept-Language": "en-US,en;q=0.8",
    "Cache-Control": "no-cache",
    "dnt": "1",
    "Pragma": "no-cache",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/5\
    37.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"
}<jupyter_output><empty_output><jupyter_text>Espaciado de peticiones HTTP Al principio, nos bastó con modificar el *user agent* para esquivar el bloqueo de peticiones. Lanzábamos peticiones ocasionalmente y nos dedicamos a analizar la estructura de la web para recoger los datos. Una vez finalizada la fase de análisis y diseño del proceso de automatización, y desplegamos la solución, Crunchbase nos denegó el acceso. Nos habían pillado.Entonces, procedimos a implementar la segunda medida prevención: **simular el comportamiento humano estableciendo un tiempo de espera** fijo. Y funcionó. Pero, ya habiendo sido bloqueados dos veces, quisimos adelantarnos a los hechos, y establecer un tiempo de espera aleatorio de entre 5 y 20 segundos para evitar que Crunchbase observara un patrón con facilidad.<jupyter_code>response = requests.get(url, stream=True, headers=headers)
root = etree.fromstring(response.content)
rows = []

for sitemap in root:
    children = sitemap.getchildren()
    # filtrar tipo de perfil
    #organization = re.match(r".+organizations-[0-9]+", children[0].text)
    organization = re.match(r".+organizations-9", children[0].text)
    if organization:
        print(children[0].text)
        r = requests.get(children[0].text,stream=True, headers=headers)
        g=gzip.GzipFile(fileobj=BytesIO(r.content))
        content=g.read()
        rootorg = etree.fromstring(content)
        count = 0
        for company in rootorg:
            if count > 500:
                childrenorg = company.getchildren()
                time.sleep(random.randint(5,20))
                rlink = requests.get(childrenorg[0].text, headers=headers)
                soup = BS(rlink.content, 'html.parser')
                # indicar por pantalla si el acceso ha sido denegado, y terminar el proceso
                if soup.head.title.text == "Access to this page has been denied.":
                    print("Access denied")
                    break
                elif len(rows) >= 1000:
                    print("Reached limit")
                    break
                # obtener la información de la empresa y añadirla a lista de registros
                row = parse2row(soup, log=True)
                rows.append(row)
            else:
                count = count + 1
        else:
            continue
        break
data = rows
df = pd.DataFrame(data)  
len(df)
df.columns
df.to_csv("cb_organizations-9_top1000.csv", index=False)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09453781512605042, 'fraction_numerical': 0.013368983957219251, 'mean_word_length': 2.8114992721979624, 'pattern_counts': {'":': 9, '<': 13, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '3', 'dir': 'jupyter-structured-clean-dedup', 'id': '97285', 'license': 'Unlicense', 'n_tokens_mistral': 1645, 'n_tokens_neox': 1599, 'n_words': 509, 'path': 'Prac1_source.ipynb', 'repo_name': 'makhfib/cb_organizations', 'size': '5236'}