成果:
包含了预览页面信息和详情页面信息
代码:
from bs4 import BeautifulSoup
from pymongo import MongoClient
import time
import requests
# 这个是创建实例
client = MongoClient()
# 这个是创建一个phone_info表
phone_info = client['phone_url']
# 这个是创建表里面的字段,需要注意的是,字段里面才存储title,url这些东西
phone_link = phone_info['phone_link']
phone_details = phone_info['phone_details']
def formatted(string):
return string.replace("\n", "").replace("\t", "").replace(" ", "")
def num_links(channel, pages):
# 'http://bj.58.com/shoujihao/pn1/'
url = '{}pn{}/'.format(channel, pages)
r = requests.get(url)
time.sleep(1)
soup = BeautifulSoup(r.text, 'lxml')
end_mark = soup.select('#infocont > span > b')[0].get_text()
if int(end_mark) != 0:
titles = soup.select('[class=boxlist] > ul > li > a.t > strong')
links = soup.select('[class=boxlist] > ul > li > a.t')
for t, l in zip(titles, links):
title = t.get_text()
link = l.get('href')
data = {
'title': title,
'link': link,
}
# phone_link是字段phone_link在py中的使用对象,插入数据
# 可以插入数据,就如下这样
phone_link.insert({'title': title, 'link': link})
else:
pass
def get_phone_info(url):
r = requests.get(url)
time.sleep(1)
soup = BeautifulSoup(r.text, 'lxml')
title = soup.select('#main > div.col.detailPrimary.mb15 > div.col_sub.mainTitle > h1')
price = soup.select(
'#main > div.col.detailPrimary.mb15 > div.col_sub.sumary > ul > li > div.su_con > span.price.c_f50')
title_text = title[0].get_text()
price_text = price[0].get_text()
data = {
'url': url,
'title': formatted(title_text),
'price': formatted(price_text)
}
phone_details.insert(data)
def main():
for page in range(1, 3):
num_links('http://bj.58.com/shoujihao/', page)
for info in phone_link.find():
print(info['link'])
url = info['link']
get_phone_info(url)
if __name__ == '__main__':
main()
大体思路:
之所以说这是准备阶段,目的之一就是爬取58上的所有信息,所以就要对于每个频道进行爬取。
而每个频道打开是预览页,预览页打开是详情页。所以,策略还是先爬取所有的预览页,存入mongodb然后再从mongodb取出link爬取详情页。基本还和之前一样,但是有几点是不一样的:
- 加入了end_mark策略。预览页由于不知道究竟多少页结束,所以我们尽可能选取非常大的pg来看看和正常页面的区别,然后取其中的不同来作为mark
- 不是for循环嵌套for循环来爬取了。而是通过数据库这个媒介来连接预览页和详情页。之前是爬取一个预览页面,就爬取这个预览页面的详情,再爬取第二个预览页面,接着爬取第二个预览页面的详情……现在是爬取所有的预览页面,存入数据库,然后这个完成之后再从数据库提取url爬取详情。
- 还有一点没有考虑,如果爬取时间过长,有些手机号被交易了,则爬取的时候会报告404错误,导致失败。这一点来看是没有加入相应策略。
新技能get:
- soup.find(‘a’)
是找到第一个符合要求的标签,即便soup中有很多<a></a>,只是返回第一个以<a id=123 class='da'>add</a>的形式返回而不是列表,后边可以加参数soup。find('a', id='123')的形式找出唯一符合的元素,
如果是某个类的话,用soup.find('a', 'da')表示,为什么不用class='da'呢?因为class是Python的关键词,又因.da亲测无用,这个记住就可以了 - soup.find()和soup.find_all()
前者讲了,后者soup.find_all('a')则返回一个列表[<a class='e' id=''>aa</a>,<a class='e' id=''>b</a>]突然发现find_all()也可以有参数find_all('a', 'e'),就表示了这个e类。 - soup.select()之class全匹配
<a class="hello world"></a>在这个路径复制出来就是a.hello.world,hello和world之间不是空格,是小数点,很神奇!!
如果还有个类<a class='hello'></a>那么用soup.select('.hello')会搜索出来所有包含hello的class,而不是全匹配,就是说class=“hello world”也会被选出来。如果要用全匹配仅仅选出来class='hello'的,就用soup.select('[class=hello]' > * >^)加上[]就好了,全匹配 - 字符串美化
抓取出来的一些字符串有很多空格或者换行符,用字符串的str.replace('pre', 'post')来将pre的字串换为post的 -
pymongo的一些操作
1.插入元素phone_details.insert(data),其中phone_details是个表,data必须是字典的格式,当然也可以phone_link.insert({'title': title, 'link': link})方式写入。
还有个phone_details.save(data),如果data存在,则更新;如果不存在,则插入
2.提取元素
phone_link.find()提取所有的信息,但是必须用for info in phone_link.find()来显示,用info[‘url’]来显示具体的。或者筛选具备一些条件的:phone_link.find({'name': 'lucy', 'age':17})多个条件用逗号来隔开
总结:
这仅仅是爬取手机号的一个分支,如果大规模爬取,还有一个main的函数,将58的首页所有channel保存到一个数据库,在每个channel分别爬取,这个在以后会说。
这次学到不少东西,加油













网友评论