python爬虫实践

模拟登陆与文件下载

爬取http://moodle.tipdm.com上面的视频并下载

模拟登陆

由于泰迪杯网站问题,测试之后发现无法用正常的账号密码登陆,这里会使用访客账号登陆。

我们先打开泰迪杯的登陆界面,打开开发者工具,选择Network选单,点击访客登陆。

注意到index.php的资源请求是一个POST请求,我们把视窗拉倒最下面,看到表单数据(Form data),浏览器在表单数据中发送了两个变量,分别是username和password,两个变量的值都是guest。这就是我们需要告诉网站的信息了。

python爬虫实践

知道了这些信息,我们就可以使用requesst来模拟登陆了。

import requests

s = requests.Session()

data = {

'username': 'guest',

'password': 'guest',

}

r = s.post('http://moodle.tipdm.com/login/index.php', data)

print(r.url)

我们引入requests包。但我们这次并没有直接使用request.post(),而是在第二行先创建了一个Session实例s,Session实例可以将浏览过程中的cookies保存下来。cookies指网站为了辨别用户身份而储存在用户本地终端上的数据(通常经过加密)。泰迪网站要聪明一点,就不是只用GET请求传递的数据来确认用户身份,而是要用保存在本地的cookies来确认用户身份(你再也不能伪装成隔壁老王了)。在requests中我们只要创建一个Session实例(比如这里的s),然后之后的请求都用Session实例s来发送,cookies的事情就不用管了。s.post()是用来发送POST请求的(requests.get()发送GET请求)。同理s.get()是用来发送get请求的。POST请求是一定要向服务器发送一个表单数据(form data)的。

我们看到泰迪网站要求上传的表单数据就是username和password,两者的值都是guest,所以在python里面我们创建一个dict,命名为data,里面的数据就输入username和password。最后再用s把数据post到网址,模拟登陆就完成了。

我们运行一下代码,

python爬虫实践

登陆成功了可以看到网址跳转到了泰迪教程的首页,和在浏览器里面的行为是一样的。这样我吗就实现了模拟登陆一个网站。

视频下载

我们进入到我们要下载的视频的页面(http://moodle.tipdm.com/course/view.php?id=16),然后对要下载的链接进行审查元素。

python爬虫实践

元素都在`a`标签中,所有这样的a标签(a tag)都在<div class="activityinstance">标签中。所以我们只要找到所有的class为acticityinstance的div标签,然后提取里面a标签的href属性,就知道视频的地址了。同样的,我们使用beautiful soup包来实现我们想要的功能。

from bs4 import BeautifulSoup

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs:

url = div.a.get('href')

print(url)

现在所有的代码看起来应该是这样的:

import requests

from bs4 import BeautifulSoup

data = {

'username': 'guest',

'password': 'guest',

}

s = requests.Session()

r = s.post('http://moodle.tipdm.com/login/index.php', data)

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs:

url = div.a.get('href')

print(url)

运行一下,

python爬虫实践

你已经拿到了所有的网址我们点开其中的一个网址,看看里面的结构:

python爬虫实践

可以看到下载链接已经在你面前了,我们对它进行审查元素,看到了一个.mp4的下载地址,那下一步我们就是要获取这个mp4的下载地址。

for div in divs[1:]:    # 注意这里也出现了改动

url = div.a.get('href')

r = s.get(url)

soup = BeautifulSoup(r.text, 'lxml')

target_div = soup.find('div', class_='resourceworkaround')

target_url = target_div.a.get('href')

print(target_url)

divs[1:]的意思是我们忽视掉divs列表(list)中的第一个元素,然后进行下面的操作。

到目前为止,你的代码看起来应该是这样的:

import requests

from bs4 import BeautifulSoup

data = {

'username': 'guest',

'password': 'guest',

}

s = requests.Session()

r = s.post('http://moodle.tipdm.com/login/index.php', data)

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs[1:]:     # 注意这里也出现了改动

url = div.a.get('href')

r = s.get(url)

soup = BeautifulSoup(r.text, 'lxml')

target_div = soup.find('div', class_='resourceworkaround')

target_url = target_div.a.get('href')

print(target_url)

运行一下代码:

python爬虫实践

恭喜你,你成功获取到了视频的下载地址。现在将我在这里提供的代码复制到你的代码前面:

def download(url, s):

import urllib, os

file_name = urllib.parse.unquote(url)

file_name = file_name[file_name.rfind('/') + 1:]

try:

r = s.get(url, stream=True, timeout = 2)

chunk_size = 1000

timer = 0

length = int(r.headers['Content-Length'])

print('downloading {}'.format(file_name))

if os.path.isfile('./' + file_name):

print('  file already exist, skipped')

return

with open('./' + file_name, 'wb') as f:

for chunk in r.iter_content(chunk_size):

timer += chunk_size

percent = round(timer/length, 4) * 100

print('\r {:4f}'.format((percent)), end = '')

f.write(chunk)

print('\r  finished    ')

except requests.exceptions.ReadTimeout:

print('read time out, this file failed to download')

return

except requests.exceptions.ConnectionError:

print('ConnectionError, this file failed to download')

return

然后在你循环的末尾加上download(target_url, s)

现在整个代码看起来是这样的:

import requests

from bs4 import BeautifulSoup

data = {

'username': 'guest',

'password': 'guest',

}

def download(url, s):

import urllib, os

file_name = urllib.parse.unquote(url)

file_name = file_name[file_name.rfind('/') + 1:]

try:

r = s.get(url, stream=True, timeout = 2)

chunk_size = 1000

timer = 0

length = int(r.headers['Content-Length'])

print('downloading {}'.format(file_name))

if os.path.isfile('./' + file_name):

print('  file already exist, skipped')

return

with open('./' + file_name, 'wb') as f:

for chunk in r.iter_content(chunk_size):

timer += chunk_size

percent = round(timer/length, 4) * 100

print('\r {:4f}'.format((percent)), end = '')

f.write(chunk)

print('\r  finished    ')

except requests.exceptions.ReadTimeout:

print('read time out, this file failed to download')

return

except requests.exceptions.ConnectionError:

print('ConnectionError, this file failed to download')

return

s = requests.Session()

r = s.post('http://moodle.tipdm.com/login/index.php', data)

r = s.get('http://moodle.tipdm.com/course/view.php?id=16')

soup = BeautifulSoup(r.text, 'lxml')

divs = soup.find_all("div", class_='activityinstance')

for div in divs[1:]:

url = div.a.get('href')

r = s.get(url)

soup = BeautifulSoup(r.text, 'lxml')

target_div = soup.find('div', class_='resourceworkaround')

target_url = target_div.a.get('href')

download(target_url, s)

运行一下,视频已经开始下载了

python爬虫实践

这样你已经成功学会了如何模拟登陆一个网站,并且学会了如何从网站上面下载一个文件。

排行榜小说批量下载

我们要爬取的是小说,排行榜的地址:http://www.qu.la/paihangbang/。先观察下网页的结构

python爬虫实践

很容易就能发现,每一个分类都是包裹在:<div class="index_toplist mright mbottom">之中,这种条理清晰的网站,大大方便了爬虫的编写。在当前页面找到所有小说的连接,并保存在列表即可。这里有个问题,就算是不同类别的小说,也是会重复出现在排行榜的。这样无形之间就会浪费爬取时间。解决方法就是:url_list = list(set(url_list)) 这里调用了一个list的构造函数set:这样就能保证列表里没有重复的元素了。

 

代码实现

 

1.网页抓取头:

import requests

from bs4 import BeautifulSoup

def get_html(url):

try:

r = requests.get(url,timeout=30)

r.raise_for_status

r.encoding='utf-8'

return r.text

except:

return 'error!'

2.获取排行榜小说及其链接:

爬取每一类型小说排行榜, 按顺序写入文件, 文件内容为:小说名字+小说链接 将内容保存到列表并且返回一个装满url链接的列表

def get_content(url):

url_list = []

html = get_html(url)

soup = BeautifulSoup(html,'lxml')

# 由于小说排版的原因,历史类和完本类小说不在一个div里

category_list = soup.find_all('div',class_='index_toplist mright mbottom')

history_list = soup.find_all('div',class_='index_toplist mbottom')

for cate in category_list:

name = cate.find('div',class_='toptab').span.text

with open('novel_list.csv','a+') as f:

f.write('\n小说种类:{} \n'.format(name))

book_list = cate.find('div',class_='topbooks').find_all('li')

# 循环遍历出每一个小说的的名字,以及链接

for book in book_list:

link = 'http://www.qu.la/' + book.a['href']

title = book.a['title']

url_list.append(link)

# 这里使用a模式写入,防止清空文件

with open('novel_list.csv','a') as f:

f.write('小说名:{} \t 小说地址:{} \n'.format(title,link))

for cate in history_list:

name = cate.find('div',class_='toptab').span.text

with open('novel_list.csv','a') as f:

f.write('\n小说种类: {} \n'.format(name))

book_list = cate.find('div',class_='topbooks').find_all('li')

for book in book_list:

link = 'http://www.qu.la/' + book.a['href']

title = book.a['title']

url_list.append(link)

with open('novel_list.csv','a') as f:

f.write('小说名:{} \t 小说地址:{} \n'.format(title,link))

return url_list

3.获取单本小说的所有章节链接:

获取该小说每个章节的url地址,并创建小说文件

# 获取单本小说的所有章节链接

def get_txt_url(url):

url_list = []

html = get_html(url)

soup = BeautifulSoup(html,'lxml')

list_a = soup.find_all('dd')

txt_name = soup.find('dt').text

with open('C:/Users/Administrator/Desktop/小说/{}.txt'.format(txt_name),'a+') as f:

f.write('小说标题:{} \n'.format(txt_name))

for url in list_a:

url_list.append('http://www.qu.la/' + url.a['href'])

return url_list,txt_name

4.获取单页文章的内容并保存到本地

从网上爬下来的文件很多时候都是带着<br>之类的格式化标签,可以通过一个简单的方法把它过滤掉:
html = get_html(url).replace('<br/>', '\n')
这里单单过滤了一种标签,并将其替换成‘\n’用于文章的换行,

def get_one_txt(url,txt_name):

html = get_html(url).replace('<br/>','\n')

soup = BeautifulSoup(html,'lxml')

try:

txt = soup.find('div',id='content').text

title = soup.find('h1').text

with open('C:/Users/Administrator/Desktop/小说/{}.txt'.format(txt.name),'a') as f:

f.write(title + '\n\n')

f.write(txt)

print('当前小说:{}当前章节{}已经下载完毕'.format(txt_name,title))

except:

print('ERROR!')

5.主函数

def get_all_txt(url_list):

for url in url_list:

# 遍历获取当前小说的所有章节的目录,并且生成小说头文件

page_list,txt_name = get_txt_url(url)

def main():

# 小说排行榜地址

base_url = 'http://www.qu.la/paihangbang/'

# 获取排行榜中所有小说的url链接

url_list = get_content(base_url)

# 除去重复的小说

url_list = list(set(url_list))

get_all_txt(url_list)

if __name__ == '__main__':

main()

6.输出结果

python爬虫实践

python爬虫实践

多线程爬虫

最近想要抓取拉勾网的数据,最开始是使用Scrapy的,但是遇到了下面两个问题:1前端页面是用JS模板引擎生成的2接口主要是用POST提交参数的

目前不会处理使用JS模板引擎生成的HTML页面,用POST的提交参数的话,接口统一,也没有必要使用Scrapy,所以就萌生了自己写一个简单的Python爬虫的想法。该爬虫框架主要就是处理网络请求,这个简单的爬虫使用多线程来处理网络请求,使用线程来处理URL队列中的url,然后将url返回的结果保存在另一个队列中,其它线程读取这个队列中的数据,然后写到文件中去。该爬虫主要用下面几个部分组成。

1 URL队列和结果队列

将将要爬取的url放在一个队列中,这里使用标准库Queue。访问url后的结果保存在结果队列中

初始化一个URL队列

from Queue import Queue

urls_queue = Queue()

out_queue = Queue()

2 请求线程

使用多个线程,不停的取URL队列中的url,并进行处理:

import threading

class ThreadCrawl(threading.Thread):

def __init__(self, queue, out_queue):

threading.Thread.__init__(self)

self.queue = queue

self.out_queue = out_queue

def run(self):

while True:

item = self.queue.get()

self.queue.task_down()

如果队列为空,线程就会被阻塞,直到队列不为空。处理队列中的一条数据后,就需要通知队列已经处理完该条数据。

3 处理线程

处理结果队列中的数据,并保存到文件中。如果使用多个线程的话,必须要给文件加上锁。

lock = threading.Lock()

f = codecs.open('out.txt', 'w', 'utf8')

当线程需要写入文件的时候,可以这样处理:

with lock:

f.write(something)

抓取结果:

python爬虫实践

源码

代码还不完善,将会持续修改中。

from Queue import Queue

import threading

import urllib2

import time

import json

import codecs

from bs4 import BeautifulSoup

urls_queue = Queue()

data_queue = Queue()

lock = threading.Lock()

f = codecs.open('out.txt', 'w', 'utf8')

class ThreadUrl(threading.Thread):

def __init__(self, queue):

threading.Thread.__init__(self)

self.queue = queue

def run(self):

pass

class ThreadCrawl(threading.Thread):

def __init__(self, url, queue, out_queue):

threading.Thread.__init__(self)

self.url = url

self.queue = queue

self.out_queue = out_queue

def run(self):

while True:

item = self.queue.get()

data = self._data_post(item)

try:

req = urllib2.Request(url=self.url, data=data)

res = urllib2.urlopen(req)

except urllib2.HTTPError, e:

raise e.reason

py_data = json.loads(res.read())

res.close()

item['first'] = 'false'

item['pn'] = item['pn'] + 1

success = py_data['success']

if success:

print 'Get success...'

else:

print 'Get fail....'

print 'pn is : %s' % item['pn']

result = py_data['content']['result']

if len(result) != 0:

self.queue.put(item)

print 'now queue size is: %d' % self.queue.qsize()

self.out_queue.put(py_data['content']['result'])

self.queue.task_done()

def _data_post(self, item):

pn = item['pn']

first = 'false'

if pn == 1:

first = 'true'

return 'first=' + first + '&pn=' + str(pn) + '&kd=' + item['kd']

def _item_queue(self):

pass

class ThreadWrite(threading.Thread):

def __init__(self, queue, lock, f):

threading.Thread.__init__(self)

self.queue = queue

self.lock = lock

self.f = f

def run(self):

while True:

item = self.queue.get()

self._parse_data(item)

self.queue.task_done()

def _parse_data(self, item):

for i in item:

l = self._item_to_str(i)

with self.lock:

print 'write %s' % l

self.f.write(l)

def _item_to_str(self, item):

positionName = item['positionName']

positionType = item['positionType']

workYear = item['workYear']

education = item['education']

jobNature = item['jobNature']

companyName = item['companyName']

companyLogo = item['companyLogo']

industryField = item['industryField']

financeStage = item['financeStage']

companyShortName = item['companyShortName']

city = item['city']

salary = item['salary']

positionFirstType = item['positionFirstType']

createTime = item['createTime']

positionId = item['positionId']

return positionName + ' ' + positionType + ' ' + workYear + ' ' + education + ' ' + \

jobNature + ' ' + companyLogo + ' ' + industryField + ' ' + financeStage + ' ' + \

companyShortName + ' ' + city + ' ' + salary + ' ' + positionFirstType + ' ' + \

createTime + ' ' + str(positionId) + '\n'

def main():

for i in range(4):

t = ThreadCrawl(

'http://www.lagou.com/jobs/positionAjax.json', urls_queue, data_queue)

t.setDaemon(True)

t.start()

datas = [

{'first': 'true', 'pn': 1, 'kd': 'Java'}

#{'first': 'true', 'pn': 1, 'kd': 'Python'}

]

for d in datas:

urls_queue.put(d)

for i in range(4):

t = ThreadWrite(data_queue, lock, f)

t.setDaemon(True)

t.start()

urls_queue.join()

data_queue.join()

with lock:

f.close()

print 'data_queue siez: %d' % data_queue.qsize()

main()

爬虫的基本流程

网络爬虫的基本工作流程如下:

●首先选取一部分精心挑选的种子URL

●将种子URL加入任务队列

●从待抓取URL队列中取出待抓取的URL,解析DNS,并且得到主机的ip,并将URL对应的网页下载下来,存储进已下载网页库中。此外,将这些URL放进已抓取URL队列。

●分析已抓取URL队列中的URL,分析其中的其他URL,并且将URL放入待抓取URL队列,从而进入下一个循环。

●解析下载下来的网页,将需要的数据解析出来。

●数据持久话,保存至数据库中。

爬虫的抓取策略

在爬虫系统中,待抓取URL队列是很重要的一部分。待抓取URL队列中的URL以什么样的顺序排列也是一个很重要的问题,因为这涉及到先抓取那个页面,后抓取哪个页面。而决定这些URL排列顺序的方法,叫做抓取策略。下面重点介绍几种常见的抓取策略:

python爬虫实践

●深度优先策略(DFS) 深度优先策略是指爬虫从某个URL开始,一个链接一个链接的爬取下去,直到处理完了某个链接所在的所有线路,才切换到其它的线路。
此时抓取顺序为:A -> B -> C -> D -> E -> F -> G -> H -> I -> J

●广度优先策略(BFS) 宽度优先遍历策略的基本思路是,将新下载网页中发现的链接直接插入待抓取URL队列的末尾。也就是指网络爬虫会先抓取起始网页中链接的所有网页,然后再选择其中的一个链接网页,继续抓取在此网页中链接的所有网页。此时抓取顺序为:A -> B -> E -> G -> H -> I -> C -> F -> J -> D

 

技术栈

requests 人性化的请求发送

Bloom Filter 布隆过滤器,用于判重

XPath 解析HTML内容

murmurhash

●Anti crawler strategy 反爬虫策略

●MySQL 用户数据存储

1 调研目标网站背景

1.1 检查robots.txt

http://example.webscraping.com/robots.txt

# section 1

User-agent: BadCrawler

Disallow: /

# section 2

User-agent: *

Crawl-delay: 5

Disallow: /trap

# section 3

Sitemap: http://example.webscraping.com/sitemap.xml

●section 1 :禁止用户代理为BadCrawler的爬虫爬取该网站,除非恶意爬虫。

●section 2 :两次下载请求时间间隔5秒的爬取延迟。/trap 用于封禁恶意爬虫,会封禁1分钟不止。

●section 3 :定义一个Sitemap文件,下节讲。

1.2 检查网站地图

所有网页链接: http://example.webscraping.com/sitemap.xml

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

<url>

<loc>http://example.webscraping.com/view/Afghanistan-1</loc>

</url>

<url>

<loc>

http://example.webscraping.com/view/Aland-Islands-2

</loc>

</url>

...

<url>

<loc>http://example.webscraping.com/view/Zimbabwe-252</loc>

</url>

</urlset>

1.3 估算网站大小

高级搜索参数:http://www.google.com/advanced_search 
Google搜索:site:http://example.webscraping.com/ 有202个网页 
Google搜索:site:http://example.webscraping.com/view 有117个网页

1.4 识别网站所有技术

用buildwith模块可以检查网站构建的技术类型。 
安装库:pip install buildwith

>>> import builtwith

>>> builtwith.parse('http://example.webscraping.com')

{u'javascript-frameworks': [u'jQuery', u'Modernizr', u'jQuery UI'],

u'web-frameworks': [u'Web2py', u'Twitter Bootstrap'],

u'programming-languages': [u'Python'],

u'web-servers': [u'Nginx']}

>>>

示例网址使用了Python的Web2py框架,还使用了JavaScript库,可能是嵌入在HTML中的。这种容易抓取。其他建构类型: 
- AngularJS:内容动态加载 
- ASP.NET:爬取网页要用到会话管理和表单提交。

基本实现

下面是一个伪代码

import Queue

initial_page = "https://www.zhihu.com/people/gaoming623"

url_queue = Queue.Queue()

seen = set()

seen.insert(initial_page)

url_queue.put(initial_page)

while(True): #一直进行

if url_queue.size()>0:

current_url = url_queue.get()                  #拿出队例中第一个的url

store(current_url)                                 #把这个url代表的网页存储好

for next_url in extract_urls(current_url):  #提取把这个url里链向的url

if next_url not in seen:

seen.put(next_url)

url_queue.put(next_url)

else:

break

如果你直接加工一下上面的代码直接运行的话,你需要很长的时间才能爬下整个知乎用户的信息,毕竟知乎有6000万月活跃用户。更别说Google这样的搜索引擎需要爬下全网的内容了。那么问题出现在哪里?

布隆过滤器

需要爬的网页实在太多太多了,而上面的代码太慢太慢了。设想全网有N个网站,那么分析一下判重的复杂度就是N*log(N),因为所有网页要遍历一次,而每次判重用set的话需要log(N)的复杂度。OK,我知道python的set实现是hash——不过这样还是太慢了,至少内存使用效率不高。通常的判重做法是怎样呢?Bloom Filter. 简单讲它仍然是一种hash的方法,但是它的特点是,它可以使用固定的内存(不随url的数量而增长)以O(1)的效率判定url是否已经在set中。可惜天下没有白吃的午餐,它的唯一问题在于,如果这个url不在set中,BF可以100%确定这个url没有看过。但是如果这个url在set中,它会告诉你:这个url应该已经出现过,不过我有2%的不确定性。注意这里的不确定性在你分配的内存足够大的时候,可以变得很小很少。

# bloom_filter.py

BIT_SIZE = 5000000

class BloomFilter:

def __init__(self):

# Initialize bloom filter, set size and all bits to 0

bit_array = bitarray(BIT_SIZE)

bit_array.setall(0)

self.bit_array = bit_array

def add(self, url):

# Add a url, and set points in bitarray to 1 (Points count is equal to hash funcs count.)

# Here use 7 hash functions.

point_list = self.get_postions(url)

for b in point_list:

self.bit_array[b] = 1

def contains(self, url):

# Check if a url is in a collection

point_list = self.get_postions(url)

result = True

for b in point_list:

result = result and self.bit_array[b]

return result

def get_postions(self, url):

# Get points positions in bit vector.

point1 = mmh3.hash(url, 41) % BIT_SIZE

point2 = mmh3.hash(url, 42) % BIT_SIZE

point3 = mmh3.hash(url, 43) % BIT_SIZE

point4 = mmh3.hash(url, 44) % BIT_SIZE

point5 = mmh3.hash(url, 45) % BIT_SIZE

point6 = mmh3.hash(url, 46) % BIT_SIZE

point7 = mmh3.hash(url, 47) % BIT_SIZE

return [point1, point2, point3, point4, point5, point6, point7]

 

建表

用户有价值的信息包括用户名、简介、行业、院校、专业及在平台上活动的数据比如回答数、文章数、提问数、粉丝数等等。

用户信息存储的表结构如下:

CREATE DATABASE `zhihu_user` /*!40100 DEFAULT CHARACTER SET utf8 */;

-- User base information table

CREATE TABLE `t_user` (

`uid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,

`username` varchar(50) NOT NULL COMMENT '用户名',

`brief_info` varchar(400)  COMMENT '个人简介',

`industry` varchar(50) COMMENT '所处行业',

`education` varchar(50) COMMENT '毕业院校',

`major` varchar(50) COMMENT '主修专业',

`answer_count` int(10) unsigned DEFAULT 0 COMMENT '回答数',

`article_count` int(10) unsigned DEFAULT 0 COMMENT '文章数',

`ask_question_count` int(10) unsigned DEFAULT 0 COMMENT '提问数',

`collection_count` int(10) unsigned DEFAULT 0 COMMENT '收藏数',

`follower_count` int(10) unsigned DEFAULT 0 COMMENT '被关注数',

`followed_count` int(10) unsigned DEFAULT 0 COMMENT '关注数',

`follow_live_count` int(10) unsigned DEFAULT 0 COMMENT '关注直播数',

`follow_topic_count` int(10) unsigned DEFAULT 0 COMMENT '关注话题数',

`follow_column_count` int(10) unsigned DEFAULT 0 COMMENT '关注专栏数',

`follow_question_count` int(10) unsigned DEFAULT 0 COMMENT '关注问题数',

`follow_collection_count` int(10) unsigned DEFAULT 0 COMMENT '关注收藏夹数',

`gmt_create` datetime NOT NULL COMMENT '创建时间',

`gmt_modify` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后一次编辑',

PRIMARY KEY (`uid`)

) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户基本信息表';

网页下载后通过XPath进行解析,提取用户各个维度的数据,最后保存到数据库中。

 

反爬虫策略应对-Headers

一般网站会从几个维度来反爬虫:用户请求的Headers,用户行为,网站和数据加载的方式。从用户请求的Headers反爬虫是最常见的策略,很多网站都会对Headers的User-Agent进行检测,还有一部分网站会对Referer进行检测(一些资源网站的防盗链就是检测Referer)。

如果遇到了这类反爬虫机制,可以直接在爬虫中添加Headers,将浏览器的User-Agent复制到爬虫的Headers中;或者将Referer值修改为目标网站域名。对于检测Headers的反爬虫,在爬虫中修改或者添加Headers就能很好的绕过。

cookies = {

"d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182",

"login": "NzM5ZDc2M2JkYzYwNDZlOGJlYWQ1YmI4OTg5NDhmMTY=|1480901173|9c296f424b32f241d1471203244eaf30729420f0",

"n_c": "1",

"q_c1": "395b12e529e541cbb400e9718395e346|1479808003000|1468847182000",

"l_cap_id": "NzI0MTQwZGY2NjQyNDQ1NThmYTY0MjJhYmU2NmExMGY=|1480901160|2e7a7faee3b3e8d0afb550e8e7b38d86c15a31bc",

"d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182",

"cap_id": "N2U1NmQwODQ1NjFiNGI2Yzg2YTE2NzJkOTU5N2E0NjI=|1480901160|fd59e2ed79faacc2be1010687d27dd559ec1552a"

}

headers = {

"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.3",

"Referer": "https://www.zhihu.com/"

}

r = requests.get(url, cookies = cookies, headers = headers)

 

反爬虫策略应对-代理IP池

还有一部分网站是通过检测用户行为,例如同一IP短时间内多次访问同一页面,或者同一账户短时间内多次进行相同操作。

大多数网站都是前一种情况,对于这种情况,使用IP代理就可以解决。这样的代理ip爬虫经常会用到,最好自己准备一个。有了大量代理ip后可以每请求几次更换一个ip,这在requests或者urllib2中很容易做到,这样就能很容易的绕过第一种反爬虫。目前知乎已经对爬虫做了限制,如果是单个IP的话,一段时间系统便会提示异常流量,无法继续爬取了。因此代理IP池非常关键。网上有个免费的代理IP API: http://api.xicidaili.com/free2016.txt

import requests

import random

class Proxy:

def __init__(self):

self.cache_ip_list = []

# Get random ip from free proxy api url.

def get_random_ip(self):

if not len(self.cache_ip_list):

api_url = 'http://api.xicidaili.com/free2016.txt'

try:

r = requests.get(api_url)

ip_list = r.text.split('\r\n')

self.cache_ip_list = ip_list

except Exception as e:

# Return null list when caught exception.

# In this case, crawler will not use proxy ip.

print e

return {}

proxy_ip = random.choice(self.cache_ip_list)

proxies = {'http': 'http://' + proxy_ip}

return proxies

后续

●使用日志模块记录爬取日志和错误日志

●分布式任务队列和分布式爬虫

爬虫源代码:zhihu-crawler 下载之后通过pip安装相关三方包后,运行$ python crawler.py即可

上一篇:python爬虫实践教学


下一篇:超赞的CSS3进度条 可以随进度显示不同颜色