디렉토리
디렉토리
라이브러리와 도구의 주요 사용
requests
aiohttp
lxml
Beautiful Soup
pyquery
asyncio
fake_useragent
pymongo
MongoDB
python3.7
I. 서론
- 웹 페이지 정보를 크롤링 코드 분석 페이지 (정보 10 크롤링);
- 에이전트 (얻기 위해 서로 다른 분석 저장소를 사용에서 경험
IP:Port
및 유형) - 선별 검사를 GET 연기;
- MongoDB를로 선별 에이전트의 성공.
II. 프로세스
(A) 분석 http://www.xicidaili.com/nn/1의 페이지 코드
1. 페이지 분석
첫 페이지로 크롤링 웹 페이지는, IP 주소, 포트를 다음과 유형 크롤링하는 데 목적을두고 있습니다.
두 번째 페이지에, 관찰 url
변경 :
찾을 수 있습니다 url
에서 http://www.xicidaili.com/nn/1 될 http://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 .
두 가지 주요 키워드 await
와 async
, 간단히 말하면, 가능성이 플러스 스레드에서 대기중인 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
인터페이스뿐만 아니라 더 복잡한 구현, 후속하는 심층 추가 될 수 있습니다 학습.