[Rastreador Python] Rastreamento paralelo de 400.000 dados de preços de casas em todo o site, e a cidade rastejante pode ser substituída


Clique aqui para pular para receber

Alguns arquivos de informações relevantes e arquivos de código podem ser encontrados diretamente no final do artigo~ Lembre-se de curtir e apoiar~

insira a descrição da imagem aqui

prefácio

Desta vez, o rastreador trata da captura de informações sobre preços de imóveis, e seu objetivo é praticar o processamento de dados e o rastreamento completo de mais de 100.000.

A sensação mais intuitiva do aumento da quantidade de dados é o aumento dos requisitos para a lógica da função.De acordo com as características do Python, a estrutura de dados é cuidadosamente selecionada. No passado, ao capturar pequenas quantidades de dados, mesmo que a parte lógica da função fosse repetida, a frequência das solicitações de E/S era intensa e o aninhamento do loop era muito profundo, a diferença era de apenas 1~2s. aumento da escala de dados, a diferença de 1~2s É possível expandir para h.

Portanto, para sites que precisam capturar uma grande quantidade de dados, podemos reduzir o custo de tempo de captura de informações em dois aspectos.

1) Otimize a lógica da função, selecione a estrutura de dados apropriada e esteja em conformidade com os hábitos de programação Pythonic. Por exemplo, para mesclagem de strings, use join() para economizar espaço de memória em vez de "+".

2) Com base em métodos de execução paralela com uso intensivo de E/S e uso intensivo de CPU, vários segmentos e vários processos são selecionados para melhorar a eficiência da execução. # 1. O que são pandas?
Exemplo: pandas é uma ferramenta baseada em NumPy criada para resolver tarefas de análise de dados.

1. Obtenha o índice

Embrulhe a solicitação e defina o tempo limite de tempo limite

# 获取列表页面
def get_page(url):
    headers = {
    
    
        'User-Agent': r'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) '
                      r'Chrome/45.0.2454.85 Safari/537.36 115Browser/6.0.3',
        'Referer': r'http://bj.fangjia.com/ershoufang/',
        'Host': r'bj.fangjia.com',
        'Connection': 'keep-alive'
    }
    timeout = 60
    socket.setdefaulttimeout(timeout)  # 设置超时
    req = request.Request(url, headers=headers)
    response = request.urlopen(req).read()
    page = response.decode('utf-8')
    return page

Localização do nível 1: informações da área

insira a descrição da imagem aqui

Posição secundária: informações da placa

insira a descrição da imagem aqui
Armazenado no dict, você pode consultar rapidamente o alvo que está procurando. -> {'Chaoyang': {'Gongti', 'Anzhen', 'Ponte Jianxiang'...}}

Localização do nível 3: informações do metrô (pesquise informações sobre moradias próximas ao metrô)

insira a descrição da imagem aqui
Adicione as informações do metrô de localização ao dict. -> {'Chaoyang': {'Gongti': {'Linha 5', 'Linha 10', 'Linha 13'}, 'Anzhen', 'Ponte Jianxiang'...}}

URL correspondente: http://bj.fangjia.com/ershoufang/–r-%E6%9C%9D%E9%98%B3%7Cw-5%E5%8F%B7%E7%BA%BF%7Cb- % E6%83%A0%E6%96%B0%E8%A5%BF%E8%A1%97

URL decodificado: http://bj.fangjia.com/ershoufang/–r-Chaoyang|w-Line 5|b-Huixin West Street

De acordo com o modo de parâmetro da url, existem duas formas de obter a url de destino:

  1. Obtenha o URL de destino de acordo com o caminho do índice

insira a descrição da imagem aqui

# 获取房源信息列表(嵌套字典遍历)
def get_info_list(search_dict, layer, tmp_list, search_list):
    layer += 1  # 设置字典层级
    for i in range(len(search_dict)):
        tmp_key = list(search_dict.keys())[i]  # 提取当前字典层级key
        tmp_list.append(tmp_key)   # 将当前key值作为索引添加至tmp_list
        tmp_value = search_dict[tmp_key]
        if isinstance(tmp_value, str):   # 当键值为url时
            tmp_list.append(tmp_value)   # 将url添加至tmp_list
            search_list.append(copy.deepcopy(tmp_list))   # 将tmp_list索引url添加至search_list
            tmp_list = tmp_list[:layer]  # 根据层级保留索引
        elif tmp_value == '':   # 键值为空时跳过
            layer -= 2           # 跳出键值层级
            tmp_list = tmp_list[:layer]   # 根据层级保留索引
        else:
            get_info_list(tmp_value, layer, tmp_list, search_list)  # 当键值为列表时,迭代遍历
            tmp_list = tmp_list[:layer]
    return search_list
  1. Envolva o URL de acordo com as informações do dict

{'Chaoyang': {'Gongti': {'Linha 5'}}}

parâmetro:

—— r-Chaoyang

—— b-corpo de trabalho

—— Linha w-5

Parâmetros de montagem: http://bj.fangjia.com/ershoufang/–r-Chaoyang|w-line 5|b-gongti

# 根据参数创建组合url
def get_compose_url(compose_tmp_url, tag_args,  key_args):
    compose_tmp_url_list = [compose_tmp_url, '|' if tag_args != 'r-' else '', tag_args, parse.quote(key_args), ]
    compose_url = ''.join(compose_tmp_url_list)
    return compose_url

2. Obtenha o número máximo de páginas na página de índice

# 获取当前索引页面页数的url列表
def get_info_pn_list(search_list):
    fin_search_list = []
    for i in range(len(search_list)):
        print('>>>正在抓取%s' % search_list[i][:3])
        search_url = search_list[i][3]
        try:
            page = get_page(search_url)
        except:
            print('获取页面超时')
            continue
        soup = BS(page, 'lxml')
        # 获取最大页数
        pn_num = soup.select('span[class="mr5"]')[0].get_text()
        rule = re.compile(r'\d+')
        max_pn = int(rule.findall(pn_num)[1])
        # 组装url
        for pn in range(1, max_pn+1):
            print('************************正在抓取%s页************************' % pn)
            pn_rule = re.compile('[|]')
            fin_url = pn_rule.sub(r'|e-%s|' % pn, search_url, 1)
            tmp_url_list = copy.deepcopy(search_list[i][:3])
            tmp_url_list.append(fin_url)
            fin_search_list.append(tmp_url_list)
    return fin_search_list

3. Pegue a etiqueta de informações da listagem

Esta é a tag que queremos pegar:

['área', 'placa', 'metrô', 'título', 'localização', 'metro quadrado', 'tipo de casa', 'andar', 'preço total', 'preço unitário do metro quadrado']
insira a descrição da imagem aqui

# 获取tag信息
def get_info(fin_search_list, process_i):
    print('进程%s开始' % process_i)
    fin_info_list = []
    for i in range(len(fin_search_list)):
        url = fin_search_list[i][3]
        try:
            page = get_page(url)
        except:
            print('获取tag超时')
            continue
        soup = BS(page, 'lxml')
        title_list = soup.select('a[class="h_name"]')
        address_list = soup.select('span[class="address]')
        attr_list = soup.select('span[class="attribute"]')
        price_list = soup.find_all(attrs={
    
    "class": "xq_aprice xq_esf_width"})  # select对于某些属性值(属性值中间包含空格)无法识别,可以用find_all(attrs={})代替
        for num in range(20):
            tag_tmp_list = []
            try:
                title = title_list[num].attrs["title"]
                print(r'************************正在获取%s************************' % title)
                address = re.sub('\n', '', address_list[num].get_text())
                area = re.search('\d+[\u4E00-\u9FA5]{2}', attr_list[num].get_text()).group(0)
                layout = re.search('\d[^0-9]\d.', attr_list[num].get_text()).group(0)
                floor = re.search('\d/\d', attr_list[num].get_text()).group(0)
                price = re.search('\d+[\u4E00-\u9FA5]', price_list[num].get_text()).group(0)
                unit_price = re.search('\d+[\u4E00-\u9FA5]/.', price_list[num].get_text()).group(0)
                tag_tmp_list = copy.deepcopy(fin_search_list[i][:3])
                for tag in [title, address, area, layout, floor, price, unit_price]:
                    tag_tmp_list.append(tag)
                fin_info_list.append(tag_tmp_list)
            except:
                print('【抓取失败】')
                continue
    print('进程%s结束' % process_i)
    return fin_info_list

4. Atribuir tarefas e buscar em paralelo

Fragmente a lista de tarefas, configure um pool de processos e busque em paralelo.

# 分配任务
def assignment_search_list(fin_search_list, project_num):  # project_num每个进程包含的任务数,数值越小,进程数越多
    assignment_list = []
    fin_search_list_len = len(fin_search_list)
    for i in range(0, fin_search_list_len, project_num):
        start = i
        end = i+project_num
        assignment_list.append(fin_search_list[start: end])  # 获取列表碎片
    return assignment_list

Ao definir o pool de processos para rastreamento em paralelo, o tempo é reduzido para 3/1 do tempo de rastreamento de processo único e o tempo total é de 3 horas.

O computador possui 4 núcleos. Após o teste, quando o número de tarefas é 3, a eficiência de execução do computador atual é a mais alta.

5. Armazene os resultados do rastreamento no Excel e aguarde o processamento dos dados visuais

# 存储抓取结果
def save_excel(fin_info_list, file_name):
    tag_name = ['区域', '板块', '地铁', '标题', '位置', '平米', '户型', '楼层', '总价', '单位平米价格']
    book = xlsxwriter.Workbook(r'C:\Users\Administrator\Desktop\%s.xls' % file_name)  # 默认存储在桌面上
    tmp = book.add_worksheet()
    row_num = len(fin_info_list)
    for i in range(1, row_num):
        if i == 1:
            tag_pos = 'A%s' % i
            tmp.write_row(tag_pos, tag_name)
        else:
            con_pos = 'A%s' % i
            content = fin_info_list[i-1]  # -1是因为被表格的表头所占
            tmp.write_row(con_pos, content)
    book.close()

insira a descrição da imagem aqui

Resumir:

Quando a escala dos dados capturados é maior, os requisitos para a lógica do programa são mais rigorosos e os requisitos para a sintaxe do python são mais proficientes. Como escrever uma gramática mais pitônica também requer aprendizado e domínio contínuos.
Adicione uma descrição da imagem

↓ ↓ ↓ Adicione o cartão de visita abaixo para me encontrar, obtenha diretamente o código-fonte e os casos ↓ ↓ ↓

Acho que você gosta

Origin blog.csdn.net/weixin_45841831/article/details/131157892
Recomendado
Clasificación