초보자를 위해 로컬 저역 에이전트 풀의 프록시 버전을 구축하는 비동기 데이터를 크롤링하여 코 루틴 하에서 기록 공정 처음 그들의

디렉토리

라이브러리와 도구의 주요 사용

  1. requests
  2. aiohttp
  3. lxml
  4. Beautiful Soup
  5. pyquery
  6. asyncio
  7. fake_useragent
  8. pymongo
  9. MongoDB
  10. python3.7

I. 서론

  1. 웹 페이지 정보를 크롤링 코드 분석 페이지 (정보 10 크롤링);
  2. 에이전트 (얻기 위해 서로 다른 분석 저장소를 사용에서 경험 IP:Port및 유형)
  3. 선별 검사를 GET 연기;
  4. MongoDB를로 선별 에이전트의 성공.

II. 프로세스

(A) 분석 http://www.xicidaili.com/nn/1의 페이지 코드

1. 페이지 분석

첫 페이지로 크롤링 웹 페이지는, IP 주소, 포트를 다음과 유형 크롤링하는 데 목적을두고 있습니다.

두 번째 페이지에, 관찰 url변경 :

찾을 수 있습니다 url에서 http://www.xicidaili.com/nn/1http://www.xicidaili.com/nn/2 그래서 다음 페이지에 링크가 그려 질 수 HTTP : // www.xicidaili.com/nn/ 페이지 대신 수치와 뒤에 몇 가지의 처음이다.

다음으로, 페이지 개발자 도구를 열 입력 Network다음과 같이 수신 된 페이지 내용에 탐색을 :

찾을 수 있습니다, 우리가 크롤링됩니다 프록시 정보는 한 쌍의 존재 <tr><\tr>는 이러한 레이블을 찾을 수 있습니다, 추가 분석을 계속 탭 class중 하나 "odd", 또는이다 "", 그러나 우리가 원하는 정보는 ip주소는 tr라벨에서 두 번째 td라벨은 상기 제 3 포트는에있는 td여섯 번째 형태에있는 레이블이 td레이블.

그런 다음 우리는 각각 기어, 해결하는 데 라이브러리를 사용하기 시작할 수 있습니다 lxml, Beautiful Soup뿐만 아니라 pyquery구문 분석하는 세 가지 일반적인 분석 라이브러리를.

가기 2. 기어

사용하여 requests라이브러리를 (나중에 때문에 이성과 프록시 테스트 프록시의 사용, 대신 비동기 요청 코 루틴 라이브러리가되었다 aiohttp, 참조 : 질문 : IP 주소가 금지 ), 첫번째에 직접 액세스를 시도 :

import requests
response = requests.get("http://www.xicidaili.com/nn/1")
print(response.text)

결과는 다음과 같다 :

<html>
<head><title>503 Service Temporarily Unavailable</title></head>
<body bgcolor="white">
<center><h1>503 Service Temporarily Unavailable</h1></center>
<hr><center>nginx/1.1.19</center>
</body>
</html>

반환 상태 코드 (503)는 서비스를 사용할 수없는, 따라서 요청 헤더에 가입하려고와 거래를 할 필요가 있음을 나타냅니다 :

import requests

header = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"}
response = requests.get("http://www.xicidaili.com/nn/1", headers = header)
print(response.text)

출력 :

<!DOCTYPE html>
<html>
<head>
  <title>国内高匿免费HTTP代理IP__第1页国内高匿</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta name="Description" content="国内高匿免费HTTP代理" />
  <meta name="Keywords" content="国内高匿,免费高匿代理,免费匿名代理,隐藏IP" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
    <meta name="applicable-device"content="pc,mobile">
    ......

이것은 일반적으로 정보 페이지를 얻을 것이다. 다음으로, 다른 구문 분석 라이브러리 페이지의 해상도를 사용하도록 선택합니다.

(ⅱ) 다른 분석 정보 라이브러리를 이용하여 크롤링

1. lxml구문 분석 라이브러리

import requests

def get_page():
    try:
        header = {'User-Agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36"}
        response = requests.get("http://www.xicidaili.com/nn/1", headers = header)
        get_detail(response.text)
    except Exception as e:
        print("发生错误: ", e)
        
# 使用lxml爬取
from lxml import etree

def get_detail(html):
    html = etree.HTML(html)
    # 爬取ip地址信息
    print(html.xpath('//tr[@class="odd" or @class=""]/td[2]/text()'))
    
if __name__ == "__main__":
    get_page()

우선, XPath는 규칙 사용하여 첫 번째 페이지의 모든 IP 주소 정보를 얻으려고 '//tr[@class="odd"]/td[2]/text()'하다이 같은 다음 추출 결과를 :

['121.40.66.129', '117.88.177.132', '117.88.176.203', '218.21.230.156', '121.31.101.41', '60.205.188.24', '221.206.100.133', '27.154.34.146', '58.254.220.116', '39.91.8.31', '221.218.102.146', '223.10.21.0', '58.56.149.198', '219.132.205.105', '221.237.37.97', '183.163.24.15', '171.80.196.14', '118.114.96.251', '114.239.91.166', '111.222.141.127', '121.237.148.133', '123.168.67.126', '118.181.226.166', '121.237.148.190', '124.200.36.118', '58.58.213.55', '49.235.253.240', '183.147.11.34', '121.40.162.239', '121.237.148.139', '121.237.148.118', '117.88.5.174', '117.88.5.234', '117.87.180.144', '119.254.94.93', '60.2.44.182', '175.155.239.23', '121.237.148.156', '118.78.196.186', '123.118.108.201', '117.88.4.71', '113.12.202.50', '117.88.177.34', '117.88.4.35', '222.128.9.235', '121.237.148.131', '121.237.149.243', '121.237.148.8', '182.61.179.157', '175.148.68.133']

결과는 오류, 당신은 포트와 종류를 얻을 수있는 동일한 방법으로되지 않습니다 :

from lxml import etree

def get_detail(html):
    html = etree.HTML(html)
    # 爬取ip地址信息
    print(html.xpath('//tr[@class="odd" or @class=""]/td[2]/text()')[:10])
    # 爬取端口信息
    print(html.xpath('//tr[@class="odd" or @class=""]/td[3]/text()')[:10])
    # 爬取类型信息
    print(html.xpath('//tr[@class="odd" or @class=""]/td[6]/text()')[:10])
    # 统计一页有多少条数据
    print(len(html.xpath('//tr[@class="odd" or @class=""]/td[6]/text()')))

그 결과, 표시 출력 데이터 (100)의 총량 :

['121.237.149.117', '121.237.148.87', '59.44.78.30', '124.93.201.59', '1.83.117.56', '117.88.176.132', '121.40.66.129', '222.95.144.201', '117.88.177.132', '121.237.149.132']
['3000', '3000', '42335', '59618', '8118', '3000', '808', '3000', '3000', '3000']
['HTTP', 'HTTP', 'HTTP', 'HTTPS', 'HTTP', 'HTTP', 'HTTP', 'HTTP', 'HTTP', 'HTTP']
100

2. Beautiful Soup구문 분석

다음과 같이 페이지 테이블 구조는 다음과 같습니다

<table id="ip_list">
    <tr>
      <th class="country">国家</th>
      <th>IP地址</th>
      <th>端口</th>
      <th>服务器地址</th>
      <th class="country">是否匿名</th>
      <th>类型</th>
      <th class="country">速度</th>
      <th class="country">连接时间</th>
      <th width="8%">存活时间</th>
      
      <th width="20%">验证时间</th>
    </tr>
  
    <tr class="odd">
      <td class="country"><img src="//fs.xicidaili.com/images/flag/cn.png" alt="Cn" /></td>
      <td>222.128.9.235</td>
      <td>59593</td>
      <td>
        <a href="/2018-09-26/beijing">北京</a>
      </td>
      <td class="country">高匿</td>
      <td>HTTPS</td>
      <td class="country">
        <div title="0.032秒" class="bar">
          <div class="bar_inner fast" style="width:87%">
            
          </div>
        </div>
      </td>
      <td class="country">
        <div title="0.006秒" class="bar">
          <div class="bar_inner fast" style="width:97%">
            
          </div>
        </div>
      </td>
      
      <td>533天</td>
      <td>20-03-13 15:21</td>
    </tr>
...

첫 번째 선택 table아래에있는 모든 tr라벨 :

from bs4 import BeautifulSoup

def get_detail(html):
    soup = BeautifulSoup(html, 'lxml')
    c1 = soup.select('#ip_list tr')
    print(c1[1])

결과는 다음과 같다 :

<tr class="odd">
<td class="country"><img alt="Cn" src="//fs.xicidaili.com/images/flag/cn.png"/></td>
<td>222.128.9.235</td>
<td>59593</td>
<td>
<a href="/2018-09-26/beijing">北京</a>
</td>
<td class="country">高匿</td>
<td>HTTPS</td>
<td class="country">
<div class="bar" title="0.032秒">
<div class="bar_inner fast" style="width:87%">
</div>
</div>
</td>
<td class="country">
<div class="bar" title="0.006秒">
<div class="bar_inner fast" style="width:97%">
</div>
</div>
</td>
<td>533天</td>
<td>20-03-13 15:21</td>
</tr>

다음 단계는 각 인 tr제 태그 ( ip), 제 (포트)와 여섯 (타입) td밖으로 선택된 라벨 :

from bs4 import BeautifulSoup

def get_detail(html):
    soup = BeautifulSoup(html, 'lxml')
    c1 = soup.select('#ip_list tr')
    ls = []
    for index, tr in enumerate(c1):
        if index != 0:
            td = tr.select('td')
            ls.append({'proxies': td[1].string + ":" + td[2].string, 
                        'types': td[5].string})
    print(ls)
    print(len(ls))

결과는 다음과 같다 :

[{'proxies': '222.128.9.235:59593', 'types': 'HTTPS'}, {'proxies': '115.219.105.60:8010', 'types': 'HTTP'}, {'proxies': '117.88.177.204:3000', 'types': 'HTTP'}, {'proxies': '222.95.144.235:3000', 'types': 'HTTP'}, {'proxies': '59.42.88.110:8118', 'types': 'HTTPS'}, {'proxies': '118.181.226.166:44640', 'types': 'HTTP'}, {'proxies': '121.237.149.124:3000', 'types': 'HTTP'}, {'proxies': '218.86.200.26:8118', 'types': 'HTTPS'}, {'proxies': '106.6.138.18:8118', 'types': 'HTTP'}......]
100

100 개 페이지 데이터가 올바른 결과이다.

3. pyquery구문 분석

pyquery분석 방법과 Beautiful Soup유사한 먼저 테이블의 첫 번째 행을 삭제 한 다음 테이블 선택 tr태그 :

from pyquery import PyQuery as pq
    
def get_detail(html):
    doc = pq(html)
    doc('tr:first-child').remove()  # 删除第一行
    items = doc('#ip_list tr')
    print(items)

로 출력에서 볼 수있는 items각 항목의 형식 :

    ...
    <tr class="">
      <td class="country"><img src="//fs.xicidaili.com/images/flag/cn.png" alt="Cn"/></td>
      <td>124.205.143.210</td>
      <td>34874</td>
      <td>
        <a href="/2018-10-05/beijing">北京</a>
      </td>
      <td class="country">高匿</td>
      <td>HTTPS</td>
      <td class="country">
        <div title="0.024秒" class="bar">
          <div class="bar_inner fast" style="width:93%">
            
          </div>
        </div>
      </td>
      <td class="country">
        <div title="0.004秒" class="bar">
          <div class="bar_inner fast" style="width:99%">
            
          </div>
        </div>
      </td>
      
      <td>523天</td>
      <td>20-03-12 02:20</td>
    </tr>
    ...

다음으로, 상기 발전기에 의해 취출 각하는 제 2 선택 td태그 ( ip주소), 제 td라벨 (포트 번호)와 여섯 번째 td태그 (타입), 사전 포맷의 목록을 저장.

from pyquery import PyQuery as pq
    
def get_detail(html):
    doc = pq(html)
    doc('tr:first-child').remove()  # 删除第一行
    items = doc('#ip_list tr')    
    ls = []
    for i in items.items():
        tmp1 = i('td:nth-child(2)') # 选取ip地址
        tmp2 = i('td:nth-child(3)') # 选取端口
        tmp3 = i('td:nth-child(6)') # 选取类型
        ls.append({'proxies': tmp1.text() + ":" + tmp2.text(),
                    'types': tmp3.text()})
    print(ls)
    print(len(ls))

출력 :

[{'proxies': '222.128.9.235:59593', 'types': 'HTTPS'}, {'proxies': '115.219.105.60:8010', 'types': 'HTTP'}, {'proxies': '117.88.177.204:3000', 'types': 'HTTP'}, {'proxies': '222.95.144.235:3000', 'types': 'HTTP'}, {'proxies': '59.42.88.110:8118', 'types': 'HTTPS'}, {'proxies': '118.181.226.166:44640', 'types': 'HTTP'}, {'proxies': '121.237.149.124:3000', 'types': 'HTTP'}, {'proxies': '218.86.200.26:8118', 'types': 'HTTPS'}......
100

한 페이지에 100 개의 검색 결과 데이터가 정확합니다.

(C)을 선택 바이 사이트를 수득 페치 프록시 테스트

필요 테스트 할 사이트를 선택 할 수 있도록 우리가 성공, 내가 선택하는 프록시 요청에 크롤링 할 수 있는지 여부를 직접 저장할 수없는, 사용 또는 불안정하기 어려운 다수가 무료 에이전트를 얻기 위해 올라 HTTP : //www.baidu.com 시험으로 만 성공적 기관이 데이터베이스에 추가 할 말을 요청합니다 요청이 폐기의 3 배 이상 수를 실패합니다.

이러한 에이전트의 검출을 위해 일반적으로 대기중인 요청이 분명히 불합리한 감지 사용하여 10 초 더 오래 일을 필요는 라이브러리 비동기 요청을 선택하는 것이 필요하다 aiohttp코 루틴에 대해 비동기, 참조 할 수 있습니다 파이썬 비동기 코 루틴 이 방법의 사용 설명aiohttp, 소개를 참조 중국어 문서를 aiohttp .

두 가지 주요 키워드 awaitasync, 간단히 말하면, 가능성이 플러스 스레드에서 대기중인 await수정 한 다음이 시간에이 곳으로 스레드가 건조 기다리고되지 않을 것이다, 그러나 실행 된 다른 작업 B를 수행하는 객체 빠른 응답 할 때까지 기다린 후, 즉시 다시 온 다음 다른 작업을 계속하고 B 작업은 일시적으로 보류. 그러나, await후자의 목적은 있어야 coroutine객체 또는 반환 할 coroutine오브젝트 생성기를 포함하거나 __await(직접없는 이유 인 방법에 의해 반환 된 반복자 객체를 requests선행 await이유). 그리고 우리는 기능을 추가 async수정 후, 함수가 객체가된다 반환 coroutine대상, 그는 "어떤 뇌가"추가 할 수 할 수 있도록 await받는 async추가하면, 물론, 포트폴리오 await장소를 요청 응답의 종류를 기다릴 필요 또는 데이터 업로드 및 다운로드를 기다리고하지 차단 된 상태로 실처럼가는 곳, 그것은 물론, 그것은 잘못되지 않을 것, 영향을 재생되지 않습니다.

다음과 같이 탐지 에이전트가 작동 :

    # 测试代理  
    async def test_proxy(self, dic):
        ## 根据类型构造不同的代理及url
        if dic["types"] == "HTTP":
            test_url = "http://www.baidu.com/"
            prop = "http://" + dic["proxies"]
        else:
            test_url = "https://www.baidu.com/"
            prop = "https://" + dic["proxies"]
        ua = UserAgent()
        header = {'User-Agent': ua.random}
        # 异步协程请求
        async with aiohttp.ClientSession() as session:
            while True:
                try:
                    async with session.get(test_url, headers = header, proxy = prop, timeout = 15, verify_ssl=False) as resp:
                        if resp.status == 200:
                            self.success_test_count += 1
                            print(prop, "\033[5;36;40m===========>测试成功,写入数据库!=========%d次\033[;;m"%self.success_test_count)
                            await self.insert_to_mongo(dic) ## 调用写入mongodb数据库的函数
                            return
                except Exception as e:
                    print(prop, "==测试失败,放弃==", e)
                    break

(D) 저장된 데이터베이스를 선택

따라서 상기 유지되고 에이전트 풀을 고려하면 데이터가 쉽게 피 복제에 삽입 될 수있는 저장 및 위해 MongoDB를 사용하도록 선택할 데이터베이스 저장 기능은 다음과 같다 :

    # 写入MongoDB数据库   
    async def insert_to_mongo(self, dic):
        db = self.client.Myproxies
        collection = db.proxies
        collection.update_one(dic,{'$set': dic}, upsert=True)   # 设置upsert=True,避免重复插入
        print("\033[5;32;40m插入记录:" + json.dumps(dic), "\033[;;m")

(E) 전체 코드

1. 프록시 크롤링 단계 버전

마지막으로, 전체 코드는 문제는이 기계가 I 있도록 요청하는 에이전트의 프록시 버전의 사용에 대한 크롤링 단계에 (이하되어 ip내가해야 할 일을했을 때문에, 프로세스가 게시 데이터를 계속 다시 크롤링, 느린 것, 폐쇄되었다 이 라이브러리를 구문 분석에 관해서) 시험의 프록시 버전을 사용하여 프록시없이 단계는,의 세 가지의 시작을 선택 lxml구문 분석 :

import json
import time
import random
from fake_useragent import UserAgent
import asyncio
import aiohttp
# 避免出现RuntimeError错误
import nest_asyncio
nest_asyncio.apply()
from lxml import etree
import pymongo

class Get_prox:
    def __init__(self):
        # 初始化,连接MongoDB
        self.client = pymongo.MongoClient('mongodb://localhost:27017/')
        self.success_get_count = 0
        self.success_test_count = 0
    
    # 使用代理时,获取页面
    async def get_page(self, session, url):
        ## 一个随机生成请求头的库        
        ua = UserAgent()
        header = {'User-Agent': ua.random}
        # 从本地文件获取代理池
        proxies_pool = self.get_proxies()
        while True:
            try:
                # 由于我一开始操作不慎ip被封禁了,因此在一开始抓取ip时我不得不使用了自己从
                # 其他网站抓来的一批代理(如问题描述中所述),一共有5999条代理,每次随机选取一条
                p = 'http://' + random.choice(proxies_pool)
                async with session.get(url, headers = header, proxy = p, timeout = 10) as response:
                    await asyncio.sleep(2)
                    if response.status == 200:
                        self.success_get_count += 1
                        print("\033[5;36;40m----------------------请求成功-------------------%d次\033[;;m"%self.success_get_count)
                        return await response.text()
                    else:
                        print("\033[5;31;m", response.status, "\033[;;m")
                        continue
            except Exception as e:
                print("请求失败orz", e)    
        
    # 任务
    async def get(self, url):
        async with aiohttp.ClientSession() as session:
            html = await self.get_page(session, url)
            await self.get_detail(html)
    
    # 测试代理  
    async def test_proxy(self, dic):
        ## 根据类型构造不同的代理及url
        if dic["types"] == "HTTP":
            test_url = "http://www.baidu.com/"
            prop = "http://" + dic["proxies"]
        else:
            test_url = "https://www.baidu.com/"
            prop = "https://" + dic["proxies"]
        ua = UserAgent()
        header = {'User-Agent': ua.random}
        # 异步协程请求
        async with aiohttp.ClientSession() as session:
            while True:
                try:
                    async with session.get(test_url, headers = header, proxy = prop, timeout = 15, verify_ssl=False) as resp:
                        if resp.status == 200:
                            self.success_test_count += 1
                            print(prop, "\033[5;36;40m===========>测试成功,写入数据库!=========%d次\033[;;m"%self.success_test_count)
                            await self.insert_to_mongo(dic) ## 调用写入mongodb数据库的函数
                            return
                except Exception as e:
                    print(prop, "==测试失败,放弃==", e)
                    break
    
    # 获取代理池
    def get_proxies(self):
        with open("proxies.txt", "r") as f:
            ls = json.loads(f.read())
        return ls
    
    # 使用lxml爬取 
    async def get_detail(self, html):
        html = etree.HTML(html)
        dic = {}
        ip = html.xpath('//tr[@class="odd" or @class=""]/td[2]/text()')
        port = html.xpath('//tr[@class="odd" or @class=""]/td[3]/text()')
        types = html.xpath('//tr[@class="odd" or @class=""]/td[6]/text()')
        for i in range(len(ip)):
            dic['proxies'] = ip[i] + ":" + port[i]
            dic['types'] = types[i]
            await self.test_proxy(dic)
        
    # 写入MongoDB数据库   
    async def insert_to_mongo(self, dic):
        db = self.client.Myproxies
        collection = db.proxies
        collection.update_one(dic,{'$set': dic}, upsert=True)   # 设置upsert=True,避免重复插入
        print("\033[5;32;40m插入记录:" + json.dumps(dic), "\033[;;m")

    
# 主线程
if __name__ == "__main__":
    urls = []
    start = time.time()
    # 抓取前10页数据
    for i in range(1, 11):
        urls.append("http://www.xicidaili.com/nn/" + str(i))
    c = Get_prox()
    # 创建10个未来任务对象
    tasks = [asyncio.ensure_future(c.get(url)) for url in urls]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    end = time.time()
    total = (end - start)/60.0
    print("完成,总耗时:", total, "分钟!")

구현 프로세스가 로그를 많이 인쇄 할뿐만 로그 부분은 다음과 같습니다

여부는 요청 프로세스 또는 테스트 프로세스, 에이전트는 ip성공률이 완전히 완성 된 실행 표시 시간이 많이 소요 47분 완료 후 약간의 시간이 매우 낮은 필요가있다 요청합니다.

로그에서 간단한 모양은, 마지막 데이터가 성공적으로 여덟 번째를 삽입 참조하십시오. . . . .

데이터베이스 나 데이터베이스의 데이터를 실행 한 후 여러 번 반복하는 동안보고, 그것은 50 삽입됩니다

에이전트 2. 크롤링 단계 버전이 사용되지 않습니다

즉, 사용 프록시 데이터의 사용없이 크롤링 단계의 게시 된 버전을 계속 requests크롤링 한 후 테스트 aiohttp스크리닝 에이전트 프로세스의 첫 단계의 대기 시간을 제거.

import json
import time
import requests
from fake_useragent import UserAgent
import asyncio
import aiohttp
# 避免出现RuntimeError错误
import nest_asyncio
nest_asyncio.apply()
from lxml import etree
import pymongo

class Get_prox:
    def __init__(self):
        # 初始化,连接MongoDB
        self.client = pymongo.MongoClient('mongodb://localhost:27017/')
        self.success_get_count = 0
        self.success_test_count = 0            
                
    # 不使用代理时,获取页面
    def get_page(self, url):
        ## 一个随机生成请求头的库        
        ua = UserAgent()
        header = {'User-Agent': ua.random}
        while True:
            try:
                response = requests.get(url, headers = header, timeout = 10)
                time.sleep(1.5)
                if response.status_code == 200:
                    self.success_get_count += 1
                    print("\033[5;36;40m----------------------请求成功-------------------%d次\033[;;m"%self.success_get_count)
                    return response.text
                else:
                    print("\033[5;31;m", response.status_code, "\033[;;m")
                    continue
            except Exception as e:
                print("请求失败orz", e)
        
    # 任务
    def get(self, urls):
        htmls = []
        # 先将抓取的页面都存入列表中
        for url in urls:
            htmls.append(self.get_page(url))
        # 测试代理使用异步
        tasks = [asyncio.ensure_future(self.get_detail(html)) for html in htmls]
        loop = asyncio.get_event_loop()
        loop.run_until_complete(asyncio.wait(tasks))
    
    # 测试代理  
    async def test_proxy(self, dic):
        ## 根据类型构造不同的代理及url
        if dic["types"] == "HTTP":
            test_url = "http://www.baidu.com/"
            prop = "http://" + dic["proxies"]
        else:
            test_url = "https://www.baidu.com/"
            prop = "https://" + dic["proxies"]
        ua = UserAgent()
        header = {'User-Agent': ua.random}
        # 异步协程请求
        async with aiohttp.ClientSession() as session:
            while True:
                try:
                    async with session.get(test_url, headers = header, proxy = prop, timeout = 15, verify_ssl=False) as resp:
                        if resp.status == 200:
                            self.success_test_count += 1
                            print(prop, "\033[5;36;40m===========>测试成功,写入数据库!=========%d次\033[;;m"%self.success_test_count)
                            await self.insert_to_mongo(dic) ## 调用写入mongodb数据库的函数
                            return
                except Exception as e:
                    print(prop, "==测试失败,放弃==", e)
                    break
    
    # 使用lxml爬取 
    async def get_detail(self, html):
        html = etree.HTML(html)
        dic = {}
        ip = html.xpath('//tr[@class="odd" or @class=""]/td[2]/text()')
        port = html.xpath('//tr[@class="odd" or @class=""]/td[3]/text()')
        types = html.xpath('//tr[@class="odd" or @class=""]/td[6]/text()')
        for i in range(len(ip)):
            dic['proxies'] = ip[i] + ":" + port[i]
            dic['types'] = types[i]
            await self.test_proxy(dic)
        
    # 写入MongoDB数据库   
    async def insert_to_mongo(self, dic):
        db = self.client.Myproxies
        collection = db.proxies
        collection.update_one(dic,{'$set': dic}, upsert=True)   # 设置upsert=True,避免重复插入
        print("\033[5;32;40m插入记录:" + json.dumps(dic) + "\033[;;m")

    
# 主线程
if __name__ == "__main__":
    urls = []
    start = time.time()
    # 抓取前10页数据
    for i in range(1, 11):
        urls.append("http://www.xicidaili.com/nn/" + str(i))
    c = Get_prox()
    c.get(urls)
    end = time.time()
    total = (end - start)/60.0
    print("完成,总耗时:", total, "分钟!")

다음과 같이 다른 작은 파트너에 의해 측정 된 결과 샷은 다음과 같습니다

단계를 크롤링 (10 개) 요청이 매우 원활했다.

마지막으로, 총 시간 19분, 단계 심사 에이전트를 크롤링의 전면에서 볼 수없는 정말 자유 시간을 많이 저장!

IV. 문제 및 해결 방법

(A) ip주소는 금지

사용의 처음부터 lxml수면 시간을 설정하기 위해, 구문 분석 규칙을 탐구하는 라이브러리를 구문 분석 할 때하는 것은 편리하지 않습니다, 나중에 부주의로 주요의 크롤링 페이지를 여러 번 크롤링 한 후, 그 결과를 절전 시간을 설정하는 것을 잊지 및 로그를 발견 다음 출력 정보 내용이된다 :

{"proxies": "121.237.148.195:3000", "types": "HTTP"}
{"proxies": "121.234.31.44:8118", "types": "HTTPS"}
{"proxies": "117.88.4.63:3000", "types": "HTTP"}
{"proxies": "222.95.144.58:3000", "types": "HTTP"}
发生错误:  'NoneType' object has no attribute 'xpath'
发生错误:  'NoneType' object has no attribute 'xpath'
发生错误:  'NoneType' object has no attribute 'xpath'
发生错误:  'NoneType' object has no attribute 'xpath'
发生错误:  'NoneType' object has no attribute 'xpath'
发生错误:  'NoneType' object has no attribute 'xpath'
......

다음과 같은 결과가 나타납니다 인쇄 응답 상태 코드를 얻을 수있는 프로그램의 종료 후 :

503
503
503
503
503
...


또한 인해 너무 많은 시간을 크롤링, 내 IP가 금지 된 웹 페이지에 그려 질 수있는 사이트에 브라우저를 통해 입력 할 수 없습니다.

  • 솔루션

처음 엔에서 몇 선택한 사이트를 통한 IP IP 다른 FA가 직접 선택하지만, 매우 큰 비중을 IP 무료 프록시 환경에서 기존 프로젝트의 프록시 IP 풀을 구축, 인터넷의 사용을 사용하지 않는 발견되었고 나는 곧장 갔다 있도록 구성은 시간이 많이 소요에 따라 달라집니다 (66) 자유 계약 네트워크 : 6000 프록시 IP를 추출하기 위해 사이트의 무료 IP 추출 기능을 사용하여,


직접 6000 프록시 정보를 클릭 추출 포함 된 페이지에서 다음, 당신은 페이지 6000 (실제로 촬영 된 5999)의 로컬 파일에 대한 프록시 정보를 크롤링이 직접을 생성하는 간단한 프로그램을 작성할 수 있습니다 :

response1 = requests.get("http://www.66ip.cn/mo.php?sxb=&tqsl=6000&port=&export=&ktip=&sxa=&submit=%CC%E1++%C8%A1&textarea=")
html = response1.text
print(response1.status_code == 200)
pattern = re.compile("br />(.*?)<", re.S)

items = re.findall(pattern, html)
for i in range(len(items)):
    items[i] = items[i].strip()
print(len(items))
with open("proxies.txt", "w") as f:
    f.write(json.dumps(items))

그런 다음 크롤러 에이전트 풀로이 파일을 읽을 수 :

# 获取代理池
    def get_proxies(self):
        with open("proxies.txt", "r") as f:
            ls = json.loads(f.read())
        return ls

그런 다음 각 요청은 무작위로 프록시 에이전트 풀을 선택 :

def get_page(ls):
    url = []
    ua = UserAgent()
    with open("proxies.txt", "r") as f:
        ls = json.loads(f.read())
    for i in range(1, page+1):
        url.append("http://www.xicidaili.com/nn/" + str(i))
    count = 1
    errcount = 1
    for u in url:
        while True:
            try:
                header = {'User-Agent': ua.random}
                handler = {'http': 'http://' + random.choice(ls)}
                response = requests.get(u, headers = header, proxies = handler, timeout = 10)
                time.sleep(1)
                get_detail(response.text)
                if response.status_code == 200:
                    print("选取ip:", handler, "请求成功---------------------------第%d次"%count)
                    count += 1
                else:
                    continue
                break
            except:
                print("选取ip:", handler, ", 第%d请求发生错误"%errcount)
                errcount += 1

그러나 스레드를 예약하는 것은 단지 작업에 대한 책임을 질 수있을 때 문제가있다, 그러나 거기에 많은 IP 프록시는 각 시도의 결과로, 사용하기 어려운 몇 초 동안 시간이 걸리지 만 않는 대부분의 경우 오류를 가지고 요청합니다.

내가 비동기 요청 라이브러리를 사용하기로 결정했습니다, 그래서이 문제를 해결하기 위해, 우리는 일정 크롤링 페이지에 단일 스레드 한 단계 접근 방식을 사용하도록 선택할 수 없습니다 aiohttp.

참조 기사는 파이썬 비동기 코 루틴 소개를 사용 하고 중국어 문서를 aiohttp , 내가 10 작업 비동기 코 루틴 스케줄러를 구현하기 위해 (작업 10 페이지를 크롤링)와 코 루틴 객체를 생성하는 것을 배웠다, 그래서 각을 스레드가 요청 된 작업을 기다리지 않고, 요구가 발생하고 다음 작업을 예약 할 수 있습니다 때, (10 개) 요청이 성공하면, 우리는 총 시간 소비가 10 배에 대해 감소 될 수 있도록, 다음 함수 호출을 입력 할 수 있습니다 (모든 기능이 목록에없는) 메서드는 다음과 같이이다 :

    # 使用代理时,获取页面
    async def get_page(self, session, url):
        ## 一个随机生成请求头的库        
        ua = UserAgent()
        header = {'User-Agent': ua.random}
        # 从本地文件获取代理池
        proxies_pool = self.get_proxies()
        while True:
            try:
                # 由于我一开始操作不慎ip被封禁了,因此在一开始抓取ip时我不得不使用了自己从
                # 其他网站抓来的一批代理(如问题描述中所述),一共有5999条代理,每次随机选取一条
                p = 'http://' + random.choice(proxies_pool)
                async with session.get(url, headers = header, proxy = p, timeout = 10) as response:
                    await asyncio.sleep(2)
                    if response.status == 200:
                        self.success_get_count += 1
                        print("\033[5;36;40m----------------------请求成功-------------------%d次\033[;;m"%self.success_get_count)
                        return await response.text()
                    else:
                        print("\033[5;31;m", response.status, "\033[;;m")
                        continue
            except Exception as e:
                print("请求失败orz", e)    
        
    # 任务
    async def get(self, url):
        async with aiohttp.ClientSession() as session:
            html = await self.get_page(session, url)
            await self.get_detail(html)
# 主线程
if __name__ == "__main__":
    urls = []
    start = time.time()
    # 抓取前10页数据
    for i in range(1, 11):
        urls.append("http://www.xicidaili.com/nn/" + str(i))
    c = Get_prox()
    # 创建10个未来任务对象
    tasks = [asyncio.ensure_future(c.get(url)) for url in urls]
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.wait(tasks))
    end = time.time()
    total = (end - start)/60.0
    print("完成,总耗时:", total, "分钟!")

인쇄 저널의 일환으로 과정을 크롤링, 눈에 보이는 성공의 요청 에이전트 확률이 매우 낮다, 다음 단계는 기다리는 것입니다 :

(B) 비동기 동작 오류 RuntimeError에러

코 루틴은 비동기 적으로 실행중인 프로그램을 시작했을 때, 에러 로그 콘솔 출력은 다음과 같습니다

RuntimeError: asyncio.run() cannot be called from a running event loop

인터넷 검색 솔루션은 프로그램의 시작 부분에 추가 :

import nest_asyncio
nest_asyncio.apply()

후 오류가되지 않으며, 특정 이유를 알 수 없습니다.

V. 추가 개선 분야

  • 하지 않는 경우 ip크롤링 무대 기관을 금지하고 직접 같이 요청, 지불의 관심은 수면 시간을 설정합니다. 사실, 다음 에이전트를 크롤링 할 때 동시에 사이트를 크롤링 다른 에이전트의 수에 그래서 당신은 비동기 요청과 함께 메커니즘을 넣을 수도에서 온 수 있습니다. 예를 들어, 각 작업은 별도로 다음 이벤트 루프 비동기 작업 코 루틴의 이동에 추가 된 웹 사이트의 요청에 대해 다른 요청을 사용하여 여러 작업을 만듭니다.
  • 내 방법이있다, 풀 프로젝트를 동적으로 유지하는 네트워크에 많은 에이전트가 정적이며, 로컬 데이터베이스에 유일한 에이전트 web인터페이스, api인터페이스뿐만 아니라 더 복잡한 구현, 후속하는 심층 추가 될 수 있습니다 학습.

추천

출처www.cnblogs.com/PanzVor/p/12497615.html