python大数据-多线程数据抓取-58同城转转网的二手产品-断点续传

目标

多线程数据抓取-58同城转转网的二手产品

实作

1. 建立一个项目

新建一个项目58tongcheng1

2. 观察页面特征

不同页面的不同规则问题
分页问题

3. 设计工作流程

首先,在主列表页爬取所有商品的URL,存储在mongodb中,在数据库中建立对应的URL_list (爬虫1)
然后,详情页具体产品的信息,存储在数据库item_info中 (爬虫2)

爬虫2应把 爬虫1抓取的并已存在数据库中的URL取出来,依次读取详情页,获得所要信息,再把这些信息存储在item_info这个表单中

4. 创建channel_extract.py获取每个分类产品的链接

python大数据-多线程数据抓取-58同城转转网的二手产品-断点续传_第1张图片
from bs4 import BeautifulSoup
import requests

start_url = 'http://bj.58.com/sale.shtml'
url_host = 'http://bj.58.com'

def get_channel_urls(url):
    wb_data = requests.get(start_url)
    soup = BeautifulSoup(wb_data.text, 'lxml')
    links = soup.select('ul.ym-submnu > li > b > a')  # 寻找该标签时比较麻烦,因为它是hover显示
    print(links)
    for link in links:
        page_url = url_host + str(link.get('href'))
        print(page_url)

get_channel_urls(start_url)  # 通过它打印出所有的URL

把所有的URL集中起来建立一个新的长字符串

channel_list = '''
http://bj.58.com/shouji/
http://bj.58.com/tongxunyw/
http://bj.58.com/danche/
http://bj.58.com/diandongche/
http://bj.58.com/fzixingche/
http://bj.58.com/sanlunche/
http://bj.58.com/peijianzhuangbei/

5. 创建page_parsing.py获取产品详情

python大数据-多线程数据抓取-58同城转转网的二手产品-断点续传_第2张图片
from bs4 import BeautifulSoup
import requests
import time
import pymongo

client = pymongo.MongoClient('localhost', 27017)
chengxu = client['chengxu']
url_list = chengxu['url_list3']
item_info = url_list['item_info3']

# spider 1 爬取首页中显示的类目中,一个类目下的所有商品的链接
def get_links_from(channel,pages,who_sells=0): # who_sells = 0表示个人,1表示商家
    #http://bj.58.com/shouji/1/pn2/
    list_view = '{}{}/pn{}/'.format(channel,str(who_sells),str(pages)) # 找网页规律的时候,刚刷新和点击后的相同页面的网址会有变化,但页面相同,它们是等价的,所以找页面规律时要多点击或刷新来找
    wb_data = requests.get(list_view)
    time.sleep(1)
    soup = BeautifulSoup(wb_data.text, 'lxml')
    if soup.find('td','t'):  # 一个类目的页码是有限的,通过寻找td.t来判断系统是否爬过头了
        for link in soup.select('td.t  a.t'):  # 这里的td.t a.t 是点击某个分类后的新网页的每个具体商品的链接的selector
        #for link in soup.select( ('td.t a.t') if not soup.find_all('zhiding', 'huishou') else None ): #修改失败,计划排除被抓取的几排广告
            # 注意!!!上面代码后面,若是('td.t >a.t')即无法显示结果,必须空格!这样才对('td.t > a.t')

            item_link = link.get('href').split('?')[0] # 这里的0是对切片后的字符串形成的列表list进行筛选,选第一段,即0(for in 就是对列表的)
            url_list.insert_one({'url': item_link })  # insert是数据库函数,注意区分
            print(item_link)
    else:
        pass
#get_links_from('http://bj.58.com/shuma/', 2)

# spider 2 爬详情页的数据
def get_item_info(url):
    wb_data = requests.get(url)
    soup = BeautifulSoup(wb_data.text, 'lxml')
    no_longer_exist = '商品已下架' in soup.find('div', "button_li").get_text() # 从下方 AAA 处移过来的代码,理解时先忽略它。
    # find()里面的代码实际是完整的div="button_li",而且要保证该段代码在正常网页和已下架网页中都存在,否则正常网页报错。
    if no_longer_exist:
        pass
    else:
        title = soup.title.text
        price = soup.select('span.price_now i')[0].text
        # 后面必须加[0].text,因为数据库要是str才能存进去,soup.select返回的对象是list,就算list里面只有一个元素,也不能用.text方法,所以才选择用[0],把元素从list调出来,再进行.text方法
        area = soup.select('.palce_li i')[0].text if soup.find_all('i') else None
        item_info.insert_one({'title':title, 'price':price, 'area':area })
        print({'title': title, 'price': price, 'area':area})

#get_item_info('http://zhuanzhuan.58.com/detail/919823388320399372z.shtml')

#======= AAA 爬取的商品链接中有失效的,剔除它(商品已交易则该网址会失效),测试完该段代码备注掉==========#
# url = 'http://zhuanzhuan.58.com/detail/922439089107222541z.shtml'  # 网址上的商品已下架
# wb_data = requests.get(url)
# soup = BeautifulSoup(wb_data.text, 'lxml')
#print(soup.prettify())

# 上面的步骤查询了失效网址的结构。
#no_longer_exist = '商品已下架' in soup.find('span', "soldout_btn").get_text()  # 搬到上方get_item_info
#print (no_longer_exist) # 查看no_longer_exist是True False。上面的find里代码必须是完整的内容,形成一个list,否则系统报错属性错误或者无法迭代

注意事项均备注在代码中。。。

6. 多进程数据抓取

建立主程序 main.py

from multiprocessing import Pool #
from channel_extract import channel_list
from page_parsing import get_links_from

def get_all_links_from(channel):
    for num in range(1,51):
        get_links_from(channel,num)

if __name__=='__main__':   # 一种类似作文开头的感谢领导的套话格式,防止上下程序串混乱了,没特别的意思
    pool = Pool()  # 创建进程池
    pool.map(get_all_links_from, channel_list.split())
    # map函数的特点是把括号内的后一个参数放到前一个参数(函数)里去依次执行。约定俗成map第一个参数为不带 () 的函数。
    # channel_list 是引用过来的,我们之前定义过它是一个长字符串,将它分成一段段,split()函数会将一个字符串自动变成分割好的一个大list

监控程序 counts.py

import time
from page_parsing import url_list # url_list 是数据库的第一张表的名称

while True:
    print(url_list.find().count())
    # find()展示url_list中所有的元素,count()计数,这两种函数是数据库函数,不能用于字典和列表
    time.sleep(5)
# 该段程序用来监控用,当它和主程序一起开的时候,它可以计算数量进程,方便管理

7. 运行

打开终端,开启3个窗口,切换到程序文件夹中,第一个窗口输入mongod,输入mongo,好了,mongo已开启
第二个窗口输入 python3 counts.py
第三个窗口输入python3 main.py
好了,开始抓取数据了,成功

python大数据-多线程数据抓取-58同城转转网的二手产品-断点续传_第3张图片

8. 断点续传

from page_parsing import get_links_from, get_item_info, url_list, item_info    # 该条为更改的,下面代码全部是新建的

# 断点续传
    db_urls = [ item['url'] for item in url_list.find() ]  # 用列表解析式装入所要爬取的链接
    index_urls = [ item['url'] for item in item_info.find() ]   # 所引出详情信息数据库中所有的现存的 url 字段
    x = set(db_urls)     # 转换成集合的数据结构
    y = set(index_urls)
    rest_of_urls = x - y

设计思路:

  1. 分两个数据库,第一个用于只用于存放抓取下来的 url (ulr_list);第二个则储存 url 对应的物品详情信息(item_info)
  2. 在抓取过程中在第二个数据库中写入数据的同时,新增一个字段(key)'index_url' 即该详情对应的链接
  3. 若抓取中断,在第二个存放详情页信息的数据库中的 url 字段应该是第一个数据库中 url 集合的子集
  4. 两个集合的 url 相减得出圣贤应该抓取的 url 还有哪些


备注

(1) find()的参数依次为(标签名,标签属性),返回一个标签(可多重嵌套)或None;

(2)find_all()的参数依次为(标签名,标签属性),返回一个标签列表或者空列表
(3) python的find()是字符串对象的方法,用于查找子字符串,返回第一个字串出现的位置或-1(字串不存在);mongodb的find()是列表对象的方法,接收字典参数,键值对为所要查找条目键值对,用于查找条目,返回True
(4) mongodb的查询方法find()find_one()
find()方法成功找到符合条件的记录则返回一个生成器(实质是停留在符合条件记录的集合的第一条记录位置的cursor),用list方法转化为列表后,如果该存在符合条件的记录,则生成一个列表,否则生成一个空列表。

find_one({查询键值对},{显示字段:0表示不显示or1表示显示,其余默认不显示,'_id'默认显示})返回查询到的第一条。

你可能感兴趣的:(python大数据-多线程数据抓取-58同城转转网的二手产品-断点续传)