python 파충류 잡기 역 의 기술 총화:진급 편(전)

본문의 출처:http://obmem.info/?p=753
Posted on November 23, 2010 by observer
전에 한 편 쓴 적 이 있어 요.
python 파충류 가 역 을 잡 는 기술 을 정리 하여 많은 파충류 가 사용 하 는 방법 을 정리 했다.그 동 동 은 지금도 매우 유용 하 게 보이 지만 그 때 는 매우 요리 가 되 었 다.이 진급 편 은'쓸 수 있다'를'편리 하고 편리 하 게 쓸 수 있다'는 단계 로 끌 어 올 릴 계획 이다.
gzip/deflate 지원
현재 웹 페이지 는 gzip 압축 을 보편적으로 지원 합 니 다.이것 은 대량의 전송 시간 을 해결 할 수 있 습 니 다.Very CD 의 홈 페이지 를 예 로 들 면 압축 되 지 않 은 버 전 247 K 는 나중에 45K 를 압축 하여 원래 의 1/5 로 합 니 다.잡기 속도 가 5 배 빠르다 는 뜻 이다.
그러나 python 의 urllib/urllib 2 는 기본적으로 압축 을 지원 하지 않 습 니 다.압축 형식 으로 돌아 가 려 면 request 의 header 에'accept-encoding'이 라 고 쓰 고 response 를 읽 은 후에 header 가'content-encoding'항목 이 있 는 지 확인 하여 디 코딩 이 필요 한 지 여 부 를 판단 해 야 합 니 다.번 거 롭 습 니 다.어떻게 하면 urllib 2 가 gzip,defalte 를 자동 으로 지원 합 니까?
사실 BaseHanlder 류 를 계승 하고 buildopener 방식 으로 처리:
import urllib2
from gzip import GzipFile
from StringIO import StringIO
class ContentEncodingProcessor(urllib2.BaseHandler):
  """A handler to add gzip capabilities to urllib2 requests """
 
  # add headers to requests
  def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req
 
  # decode
  def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
        gz = GzipFile(
                    fileobj=StringIO(resp.read()),
                    mode="r"
                  )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
        resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
        gz = StringIO( deflate(resp.read()) )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)  # 'class to add info() and
        resp.msg = old_resp.msg
    return resp
 
# deflate support
import zlib
def deflate(data):   # zlib only provides the zlib compress format, not the deflate format;
  try:               # so on top of all there's this workaround:
    return zlib.decompress(data, -zlib.MAX_WBITS)
  except zlib.error:
    return zlib.decompress(data)

그리고 간단 해..
encoding_support = ContentEncodingProcessor
opener = urllib2.build_opener( encoding_support, urllib2.HTTPHandler )
 
#   opener    ,       gzip/defalte      
content = opener.open(url).read()

2.더욱 편리 하 게 다 중 스 레 드
한 글 을 요약 하면 간단 한 다 중 스 레 드 템 플 릿 을 언급 했 지만 그 동 동 은 프로그램 에 진정 으로 응용 하면 프로그램 을 지리멸렬 하 게 만 들 고 눈 에 띄 지 않 을 것 이다.어떻게 하면 더 편리 하 게 다 중 스 레 드 를 진행 할 수 있 는 지 에 대해 나 도 머리 를 썼 다.먼저 다 중 스 레 드 호출 을 어떻게 하 는 것 이 가장 편리 한 지 생각해 보 세 요.
1.twisted 로 비동기 I/O 캡 처
사실 더 효율 적 인 캡 처 는 반드시 다 중 스 레 드 를 사용 해 야 하 는 것 이 아니 라 비동기 I/O 법 을 사용 할 수 있 습 니 다.twisted 의 getPage 방법 을 직접 사용 한 다음 에 비동기 I/O 가 끝 날 때의 callback 과 errback 방법 을 각각 추가 하면 됩 니 다.예 를 들 어 이렇게 할 수 있다.
from twisted.web.client import getPage
from twisted.internet import reactor
 
links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
 
def parse_page(data,url):
    print len(data),url
 
def fetch_error(error,url):
    print error.getErrorMessage(),url
 
#       
for url in links:
    getPage(url,timeout=5) /
        .addCallback(parse_page,url) / #     parse_page  
        .addErrback(fetch_error,url)     #     fetch_error  
 
reactor.callLater(5, reactor.stop) #5     reactor    
reactor.run()

tisted 사람 은 이름 그대로 코드 가 너무 왜곡 되 어 비정 상 사람들 이 받 아들 일 수 있 습 니 다.비록 이 간단 한 예 는 괜찮아 보이 지만.매번 tisted 를 쓰 는 프로그램 은 모두 가 왜곡 되 어 매우 피곤 하 다.문 서 는 없 는 것 과 같다.반드시 소스 코드 를 봐 야 어떻게 하 는 지 알 수 있다.아,말 도 마 세 요.
gzip/deflate 를 지원 하고 로그 인 확장 까지 하려 면 twisted 에 새로운 HTTP Client Factory 류 를 써 야 합 니 다.저 는 이 눈썹 이 정말 구 겨 져 서 포기 합 니 다.끈기 가 있 는 자 는 스스로 시도 해 보 세 요.
트 위 스 티 드 로 대량 사이트 처 리 를 어떻게 하 는 지 에 대한 이 글 은 좋 습 니 다.얕 은 것 에서 깊 은 것 으로,깊 은 것 에서 얕 은 것 으로,한 번 볼 수 있 습 니 다.
2.간단 한 다 중 스 레 드 캡 처 클래스 를 설계 합 니 다.
아니면 urllib 같은 python'본토'의 동쪽 에서 괴 롭 히 는 것 이 더 편 하 다 고 생각 합 니까?생각해 보 세 요.만약 Fetcher 류 가 있다 면,당신 은 이렇게 호출 할 수 있 습 니 다.
f = Fetcher(threads=10) #        10
for url in urls:
    f.push(url)  #   url      
while f.taskleft(): #           
    content = f.pop()  #            
    do_with(content) #   content  

이렇게 다 중 스 레 드 호출 은 간단명료 합 니 다.그러면 이렇게 디자인 합 시다.먼저 두 개의 대기 열 이 있어 야 합 니 다.Queue 로 해결 해 야 합 니 다.다 중 스 레 드 의 기본 구조 도'기술 정리'라 는 글 과 유사 합 니 다.push 방법 과 pop 방법 은 모두 Queue 를 직접 사용 하 는 방법 입 니 다.taskleft 는'실행 중인 작업'이나'대기 열 에 있 는 작업'이 있 으 면'하기 도 쉽 습 니 다.그래서 코드 는 다음 과 같 습 니 다.
import urllib2
from threading import Thread,Lock
from Queue import Queue
import time
 
class Fetcher:
    def __init__(self,threads):
        self.opener = urllib2.build_opener(urllib2.HTTPHandler)
        self.lock = Lock() #   
        self.q_req = Queue() #    
        self.q_ans = Queue() #    
        self.threads = threads
        for i in range(threads):
            t = Thread(target=self.threadget)
            t.setDaemon(True)
            t.start()
        self.running = 0
 
    def __del__(self): #            
        time.sleep(0.5)
        self.q_req.join()
        self.q_ans.join()
 
    def taskleft(self):
        return self.q_req.qsize()+self.q_ans.qsize()+self.running
 
    def push(self,req):
        self.q_req.put(req)
 
    def pop(self):
        return self.q_ans.get()
 
    def threadget(self):
        while True:
            req = self.q_req.get()
            with self.lock: #          ,  critical area
                self.running += 1
            try:
                ans = self.opener.open(req).read()
            except Exception, what:
                ans = ''
                print what
            self.q_ans.put((req,ans))
            with self.lock:
                self.running -= 1
            self.q_req.task_done()
            time.sleep(0.1) # don't spam
 
if __name__ == "__main__":
    links = [ 'http://www.verycd.com/topics/%d/'%i for i in range(5420,5430) ]
    f = Fetcher(threads=10)
    for url in links:
        f.push(url)
    while f.taskleft():
        url,content = f.pop()
        print url,len(content)

3.사소한 경험
1.연결 탱크:
opener.open 은 urllib 2.urlopen 과 마찬가지 로 http 요청 을 새로 만 듭 니 다.일반적인 상황 에서 이것 은 문제 가 되 지 않 습 니 다.선형 환경 에서 1 초 만 에 요청 이 새로 생 성 될 수 있 기 때 문 입 니 다.그러나 다 중 스 레 드 환경 에서 매 초 에 몇 십 수백 개의 요청 이 있 을 수 있 습 니 다.이렇게 하면 몇 분 이면 정상 적 인 이성 적 인 서버 가 반드시 차단 할 것 입 니 다.
그러나 정상 적 인 html 요청 시 서버 와 수 십 개의 연결 을 동시에 유지 하 는 것 은 정상 적 인 일이 기 때문에 HttpConnection 의 풀 을 수 동 으로 유지 한 다음 캡 처 할 때마다 연결 풀 에서 연결 을 선택 하여 연결 하면 됩 니 다.
여기 서 교묘 한 방법 이 있 습 니 다.바로 squid 를 프 록 시 서버 로 캡 처 하면 squid 는 자동 으로 연결 풀 을 유지 하고 데이터 캐 시 기능 도 추가 합 니 다.그리고 squid 는 원래 제 모든 서버 에 필요 한 것 입 니 다.더 이상 귀 찮 게 연결 풀 을 쓸 필요 가 있 습 니까?
2.스 레 드 의 스 택 크기 설정
스 택 크기 의 설정 은 python 의 메모리 점용 에 현저 한 영향 을 줄 것 입 니 다.python 다 중 스 레 드 가 이 값 을 설정 하지 않 으 면 프로그램 이 대량의 메모 리 를 점용 할 수 있 습 니 다.이것 은 openvz 의 vps 에 매우 치 명 적 입 니 다.stack_size 는 32768 이상 이 어야 하 며,실제로는 32768*2 이상 이 어야 합 니 다.
from threading import stack_size
stack_size(32768*16)

3、설정 실패 후 자동 재 시도
    def get(self,req,retries=3):
        try:
            response = self.opener.open(req)
            data = response.read()
        except Exception , what:
            print what,req
            if retries>0:
                return self.get(req,retries-1)
            else:
                print 'GET Failed',req
                return ''
        return data

4.설정 시간 초과
    import socket
    socket.setdefaulttimeout(10) #  10      

5.로그 인
로그 인 이 더욱 간소화 되 었 습 니 다.우선 buildopener 에 쿠키 지원 을 추가 하려 면'총화'라 는 글 을 참고 하 십시오.Very CD 에 로그 인 하려 면 Fetcher 에 빈 방법 login 을 추가 하고init__()중간 호출,그리고 Fetcher 클래스 를 계승 하고 override login 방법:
def login(self,username,password):
    import urllib
    data=urllib.urlencode({'username':username,
                           'password':password,
                           'continue':'http://www.verycd.com/',
                           'login_submit':u'  '.encode('utf-8'),
                           'save_cookie':1,})
    url = 'http://www.verycd.com/signin'
    self.opener.open(url,data).read()

그래서 Fetcher 가 초기 화 될 때 자동 으로 Very CD 사이트 에 접속 합 니 다.
총화
이렇게 하면 상기 모든 작은 기 교 를 융합 시 키 면 현재 제 가 숨 겨 놓 은 최종 판 의 Fetcher 류 와 차이 가 멀 지 않 습 니 다.다 중 스 레 드,gzip/deflate 압축,시간 초과 설정,자동 재 시도,스 택 크기 설정,자동 로그 인 등 기능 을 지원 합 니 다.코드 가 간단 하고 사용 하기에 편리 하 며 성능 도 뛰 어 나 집에 서 여행 하고 사람 을 죽 이 고 불 을 지 르 며 기침 을 하 는 데 필수 적 인 도구 라 고 할 수 있 습 니 다.
최종 판 과 크게 다 르 지 않 은 이 유 는 최종 판 에 보존 기능 인'조끼 술'이 있 기 때문이다.다 중 에이전트 가 자동 으로 선택 하기 때문이다.보기 에는 random.choice 의 차이 일 뿐 인 것 같 지만 사실은 대리 획득,대리 검증,대리 속도 측정 등 여러 부분 을 포함 하고 있 습 니 다.이것 이 바로 다른 이야기 입 니 다.

좋은 웹페이지 즐겨찾기